I have a backend service which accepts Authorization header to validate access. I created a Gateway service with Spring cloud zuul and routed requests to backend service.
Gateway service itself is protected with OAuth2 and accepts a Authorization header to validate access. Now once the request authorizes to gateway service, Gateway service act as a client to send an updated Access token in the header by using it's clientid,secret and backend service resource ID. As it has to send Authorization header, I was trying to update the Authorization header in the request but it is taking for below two ways.
ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("Authorization", accessToken);
With the above code it is adding Authorization header but it is adding it to zuul headers which the backend service is not identiyfying.
I have created a wrapper to modify the request object but it is not working
public class RequestWrapper extends HttpServletRequestWrapper
{
private final Map<String, String[]> modifiableParameters;
private Map<String, String[]> allParameters = null;
public RequestWrapper(final HttpServletRequest request,
final Map<String, String[]> additionalParams)
{
super(request);
modifiableParameters = new TreeMap<String, String[]>();
modifiableParameters.putAll(additionalParams);
}
#Override
public String getParameter(final String name)
{
String[] strings = getParameterMap().get(name);
if (strings != null)
{
return strings[0];
}
return super.getParameter(name);
}
#Override
public Map<String, String[]> getParameterMap()
{
if (allParameters == null)
{
allParameters = new TreeMap<String, String[]>();
allParameters.putAll(super.getParameterMap());
allParameters.putAll(modifiableParameters);
}
return Collections.unmodifiableMap(allParameters);
}
#Override
public Enumeration<String> getParameterNames()
{
return Collections.enumeration(getParameterMap().keySet());
}
#Override
public String[] getParameterValues(final String name)
{
return getParameterMap().get(name);
}
}
Above wrapper is found from http://www.ocpsoft.org/opensource/how-to-safely-add-modify-servlet-request-parameter-values/
and called it in the zuul filter
authToken = getAuthToken();
String accessToken = "Bearer " + authToken;
Map<String,String[]> additionalParams = new HashMap<>();
additionalParams.put("Authorization", new String[] {accessToken});
ctx.setRequest(new RequestWrapper(request, additionalParams));
What am I doing wrong or any other way of modifying request header?
if you are asking about how to add headers to Zuul request, your code is correct but you should verify the Filter type (Pre, Route, Post, ...) and the ORDER of your filter.
Check this thread : Adding Headers to Zuul when re-directing
And maybe this can helps too : How to select route based on header in Zuul
If you need to pass authorization through Zuul to backend service, you can check sensitiveHeaders property. Something like :
zuul.routes.YOURSERVICE.sensitiveHeaders=Cookie,Set-Cookie
Related
I'm attempting to create a webhook to receive messages from a Twilio phone number. But instead of just needing a webhook that will modify the data and immediately return a result to Twilio, I need this webhook to pass Twilio's message into an internal API, wait for the response, and then return the result to Twilio.
Here's some generic code I came up with that I hoped would work.
public async Task<HttpResponseMessage> ReceiveAndForwardSms(HttpContent smsContent)
{
var client = new HttpClient();
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", smsContent);
return response;
}
The problem with this code is that Twilio immediately returns a 415 error code (Unsupported Media Type) before entering the function.
When I try to accept the "correct type" (Twilio.AspNet.Common.SmsRequest), I am unable to stuff the SmsRequest back into a form-encoded object and send it via client.PostAsync()...
Ex.:
public async Task<HttpResponseMessage> ReceiveAndForwardSms([FromForm]SmsRequest smsRequest)
{
var client = new HttpClient();
var stringContent = new StringContent(smsRequest.ToString());
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", stringContent);
return response;
}
Is there anything I can do to "mask" the function's accepted type or keep this first function generic?
How do I go about shoving this SmsRequest back into a "form-encoded" object so I can accept it the same way in my consuming service?
TLDR
Your options are:
Use an existing reverse proxy like NGINX, HAProxy, F5
Use YARP to add reverse proxy functionality to an ASP.NET Core project
Accept the webhook request in a controller, map the headers and data to a new HttpRequestMessage and send it to your private service, then map the response of your private service, to the response back to Twilio.
It sounds like what you're trying to build is a reverse proxy. It is very common to put a reverse proxy in front of your web application for SSL termination, caching, routing based on hostname or URL, etc.
The reverse proxy will receive the Twilio HTTP request and then forwards it to the correct private service. The private service responds which the reverse proxy forwards back to Twilio.
I would recommend using an existing reverse proxy instead of building this functionality yourself. If you really want to build it yourself, here's a sample I was able to get working:
In your reverse proxy project, add a controller as such:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace ReverseProxy.Controllers;
public class SmsController : Controller
{
private static readonly HttpClient HttpClient;
private readonly ILogger<SmsController> logger;
private readonly string twilioWebhookServiceUrl;
static SmsController()
{
// don't do this in production!
var insecureHttpClientHandler = new HttpClientHandler();
insecureHttpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true;
HttpClient = new HttpClient(insecureHttpClientHandler);
}
public SmsController(ILogger<SmsController> logger, IConfiguration configuration)
{
this.logger = logger;
twilioWebhookServiceUrl = configuration["TwilioWebhookServiceUrl"];
}
public async Task Index()
{
using var serviceRequest = new HttpRequestMessage(HttpMethod.Post, twilioWebhookServiceUrl);
foreach (var header in Request.Headers)
{
serviceRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
serviceRequest.Content = new FormUrlEncodedContent(
Request.Form.ToDictionary(
kv => kv.Key,
kv => kv.Value.ToString()
)
);
var serviceResponse = await HttpClient.SendAsync(serviceRequest);
Response.ContentType = "application/xml";
var headersDenyList = new HashSet<string>()
{
"Content-Length",
"Date",
"Transfer-Encoding"
};
foreach (var header in serviceResponse.Headers)
{
if(headersDenyList.Contains(header.Key)) continue;
logger.LogInformation("Header: {Header}, Value: {Value}", header.Key, string.Join(',', header.Value));
Response.Headers.Add(header.Key, new StringValues(header.Value.ToArray()));
}
await serviceResponse.Content.CopyToAsync(Response.Body);
}
}
This will accept the Twilio webhook request, and forward all headers and content to the private web service. Be warned, even though I was able to hack this together until it works, it is probably not secure and not performant. You'll probably have to do a lot more to get this to become production level code. Use at your own risk.
In the ASP.NET Core project for your private service, use a TwilioController to accept the request:
using Microsoft.AspNetCore.Mvc;
using Twilio.AspNet.Common;
using Twilio.AspNet.Core;
using Twilio.TwiML;
namespace Service.Controllers;
public class SmsController : TwilioController
{
private readonly ILogger<SmsController> logger;
public SmsController(ILogger<SmsController> logger)
{
this.logger = logger;
}
public IActionResult Index(SmsRequest smsRequest)
{
logger.LogInformation("SMS Received: {SmsId}", smsRequest.SmsSid);
var response = new MessagingResponse();
response.Message($"You sent: {smsRequest.Body}");
return TwiML(response);
}
}
Instead of proxying the request using the brittle code in the reverse proxy controller, I'd recommend installing YARP in your reverse proxy project, which is an ASP.NET Core based reverse proxy library.
dotnet add package Yarp.ReverseProxy
Then add the following configuration to appsettings.json:
{
...
"ReverseProxy": {
"Routes": {
"SmsRoute" : {
"ClusterId": "SmsCluster",
"Match": {
"Path": "/sms"
}
}
},
"Clusters": {
"SmsCluster": {
"Destinations": {
"SmsService1": {
"Address": "https://localhost:7196"
}
}
}
}
}
}
This configuration will forward any request to the path /Sms, to your private ASP.NET Core service, which on my local machine is running at https://localhost:7196.
You also need to update your Program.cs file to start using YARP:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
When you run both projects now, the Twilio webhook request to /sms is forwarded to your private service, your private service will respond, and your reverse proxy service will forward the response back to Twilio.
Using YARP you can do a lot more through configuration or even programmatically, so if you're interested I'd check out the YARP docs.
If you already have a reverse proxy like NGINX, HAProxy, F5, etc. it may be easier to configure that to forward your request instead of using YARP.
PS: Here's the source code for the hacky and YARP solution
I'm implementing a custom ASP.NET Core middleware to handle the ETag/If-Match pattern to prevent lost updates. My HTTP GET operations will return an ETag value in the header and every PUT operation will be required to include that ETag in the If-Match header. I'm hashing the body of the GET responses to generate the ETag value. I've currently implemented the middleware using the HttpClient to perform the GET operation when I need to check the If-Match header. This works but requires a network/out-of-process call. Shouldn't there be a better performing way to call the ASP.NET Core HTTP pipeline without leaving the process? Can the application generate a new request to itself without doing IO? Here's the code currently for my middleware:
public class ETagIfMatchMiddleware : IMiddleware
{
//client for my asp.net core application
public static HttpClient client = new HttpClient { BaseAddress = new Uri("https://localhost:5001") };
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
if (request.Method == HttpMethods.Put)
{
var ifMatch = request.Headers[HeaderNames.IfMatch];
//requires network out of process call
var response = await client.GetAsync(request.GetEncodedUrl());
string eTag = response.Headers.ETag.Tag;
if (eTag != ifMatch)
{
context.Response.StatusCode = StatusCodes.Status412PreconditionFailed;
return;
}
}
await next(context);
}
}
I’m working on a spring boot project with GraphQL. I'm using graphql-java-tools and graphql-spring-boot-starter. I managed to configure security and session management with spring security as you can see in the java config files below.
Now the “/graphql” path is secured (it can be accessed only sending the “basic http authentication” or a session token (x-auth-token) in a http header of the request). Authenticating with “basic http authentication” on any GraphQL operation will start a new session and send back the new session token in a header, and that token can be used further to continue that session.
How to give access to anonymous users to some GraphQL queries/mutations keeping the above behavior?
If I change antMatchers("/graphql").authenticated() to antMatchers("/graphql").permitAll() in order to allow anonymous access, then my custom AuthenticationProvider is not called anymore even when I try to authenticate with “basic http authentication”.
Thanks!
Here are my configs:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/graphql").authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic()
.and()
.headers()
.frameOptions().sameOrigin() // needed for H2 web console
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 180)
public class HttpSessionConfig {
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
Instead of .antMatchers("/graphql").authenticated() we used .antMatchers("/graphql").permitAll(), then we removed .httpBasic() and also removed the custom AuthenticationProvider. Now the security configs look like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/graphql").permitAll()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.headers()
.frameOptions().sameOrigin() // needed for H2 web console
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
Then we created a mutation for login that accepts the user's credentials and returns the session token. Here is the graphql schema:
login(credentials: CredentialsInputDto!): String
input CredentialsInputDto {
username: String!
password: String!
}
Basically the code we had in our custom AuthenticationProvider went into the service that is called by the login operation:
public String login(CredentialsInputDto credentials) {
String username = credentials.getUsername();
String password = credentials.getPassword();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
... credential checks and third party authentication ...
Authentication authentication = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
httpSession.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
return httpSession.getId();
}
The key is that we prepared the session context with the authenticated user's authentication and then we save it (in redis) as a session attribute called "SPRING_SECURITY_CONTEXT". This is all what spring needs to be able to automatically restore the context when you make a request having the "x-auth-token" header set with the value of the session token obtained from the login operation.
Now also anonymous calls are allowed because of .antMatchers("/graphql").permitAll() and in the service layer, on public methods we can use annotations like this: #Preauthorize("isAnonymous() OR hasRole("USER")").
Even though you need to use permitAll() you can still create reasonable default for your resolver methods using AOP.
You can create your custom security aspect that will require authentication by default.
Unsecured methods may be marked for example using annotation.
See my blog post for details: https://michalgebauer.github.io/spring-graphql-security
NOTE: I have many questions littered in the code comments below. I need answers to those as well.
I have read (among many others) the following articles:
http://blogs.msdn.com/b/hongmeig1/archive/2012/05/11/how-to-write-a-custom-parameter-binding-to-construct-an-object-either-from-body-or-from-uri-s-query.aspx
http://blogs.msdn.com/b/jmstall/archive/2012/05/11/webapi-parameter-binding-under-the-hood.aspx
I would like for my web api to have authentication sent in the header using the Authorization header. I would like this header to be populated into a c# class called AuthenticationToken. Then when I am doing parameter binding I would like to retreive this previously created AuthenticationToken object and pass it on to my controller action. For example, if I have the following controller
public class MyServiceController : ApiController {
readonly ISecurityService _security;
readonly IMyService _myService;
// constructor values are injected
public MyServiceController(ISecurityService security, IMyService myService) {
_security = security;
_myService = myService;
}
public SomeData GetASpecificItem(AuthenticationToken token, int id) {
if (_security.IsAuthorized(token, Permissions.Read)) {
return myService.DoStuffToGetSomeData(token);
} else {
var msg = new HttpResponseMessage(HttpStatusCode.Forbidden);
throw new HttpResponseException(msg);
}
}
}
and the following parameter binding class
public class AuthenticationTokenParameterBinding
: HttpParameterBinding { // do I need to inherit from a different class?
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
HttpActionContext actionContext,
CancellationToken cancellationToken) {
try {
AuthenticationToken token; // UPDATED: how can i get this from the data
// available from inside this method?
SetValue(actionContext, token);
// is this the correct task to return on successfull parameter binding?
return base.ExecuteBindingAsyn(metadataProvider, actionContext, cancellationToken);
} catch {
return Task<HttpResponseMessage>.Factory.StartNew(() => {
var hpm = new HttpResponseMessage(HttpStatusCode.Unauthorized);
hpm.Headers.Add("WWW-Authenticate","MyCustomScheme");
return hpm;
});
}
}
}
If these two are implemented correctly, then the controller will automatically get the AuthenticationToken instance that was created during authorization.
I do not know where to authenticate ahead of this process. Nor do I know how to pass an object between authentication and authorization.
UPDATE:
I can't use a custom AuthorizeAttribute because authorization may be against an object:
public SaveResponse Save(AuthenticationToken user, SomeObjectThatNeedsToBeSaved obj) {
// NOTE: permissions are checked between the object and the user, not a role
if (_security.IsAuthorized(user, obj, Permission.Modify, Permission.Create)) {
// NOTE: other permissions we don't know about may need to be checked in the service call
return new SaveResponse {
Success = ISomeService.Save(user, obj); // bool return value
}
} else {
// return 403 Forbidden }
}
I need to pass the token to the controller action, but I also need to authenticate the token before it gets passed to the controller. Since all of this is not necessarily role based, I don't see how I can authenticate from inside of a custom AuthorizeAttribute
I have used a custom AuthorizeAttribute to handle both authentication and authorization for Web API. This attribute works as a filter and will process the request before it gets to your Web API method. In the overridden OnAuthorize method you can return HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) if authentication fails and HttpResponseMessage(System.Net.HttpStatusCode.Forbidden) if authorization fails so that the client can distinguish between both types of errors. In addition to the custom AuthorizeAttribute I implemented a custom MembershipProvider and RoleProvider to handle my specific security requirements and custom database schema.
I use basic authentication to pass the credentials for authorization. This puts the credentials in the header. To do this is pretty straight forward by using the beforeSend event handler of the JQuery ajax function. Here is an example of how to do this.
getAuthorizationHeader = function (username, password) {
var authType;
var up = $.base64.encode(username + ":" + password);
authType = "Basic " + up;
};
return authType;
};
$.ajax({
url: _url,
data: _data,
type: _type,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", getAuthorizationHeader(username, password));
},
success: ajaxSuccessHandler,
error: ajaxErrHandler
});
This encodes the username/password that is sent in the header. Note that this is not enough security to rely on just the encoding as it is easy to decode. You still want to use HTTPS/SSL to make sure the information sent over the wire is secure.
On the Web API side you can make a custom AuthorizeAttribute that gets the credentials from the header, decodes them, and performs your authorization process. There is a separate AuthorizeAttribute used by the Web API as opposed to the controller. Be sure to use System.Web.Http.AuthorizeAttribute as your base class when creating your custom AuthorizeAttribute. They have different behaviors. The one for the controller will want to redirect to the logon page whereas the one for the Web API returns an HTTP code indicating success or failure. I return an HTTP code of Forbidden if authorization fails to distinguish a failure due to authorization as opposed to authentication so the client can react accordingly.
Here is an example method for getting the credentials from the header that can be used in the custom AuthorizeAttribute.
private bool GetUserNameAndPassword(HttpActionContext actionContext, out string username, out string password)
{
bool gotIt = false;
username = string.Empty;
password = string.Empty;
IEnumerable<string> headerVals;
if (actionContext.Request.Headers.TryGetValues("Authorization", out headerVals))
{
try
{
string authHeader = headerVals.FirstOrDefault();
char[] delims = { ' ' };
string[] authHeaderTokens = authHeader.Split(new char[] { ' ' });
if (authHeaderTokens[0].Contains("Basic"))
{
string decodedStr = SecurityHelper.DecodeFrom64(authHeaderTokens[1]);
string[] unpw = decodedStr.Split(new char[] { ':' });
username = unpw[0];
password = unpw[1];
}
gotIt = true;
}
catch { gotIt = false; }
}
return gotIt;
}
And here is the code for decoding the header data that is used in this method.
public static string DecodeFrom64(string encodedData)
{
byte[] encodedDataAsBytes
= System.Convert.FromBase64String(encodedData);
string returnValue =
System.Text.Encoding.ASCII.GetString(encodedDataAsBytes);
return returnValue;
}
Once you have the username and password you can perform your authorization process and return the appropriate HTTP code to the client for handling.
You could perform the a similar process with your custom token, or you can leverage the cookie that is passed back and forth if you do not want to keep the password/username stored in the client.
I need to authenticate every request to wcf services
public class AuthenticationInterceptor : RequestInterceptor
{
public AuthenticationInterceptor() : base(false)
{
}
public override void ProcessRequest(ref System.ServiceModel.Channels.RequestContext requestContext)
{
//How to access Request Header (Authorization header) from here?
}
}
You can get the headers from the System.ServiceModel.Channels.Message, so try
var message = requestContext.RequestMessage;
var request = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
string authorization = request.Headers[HttpRequestHeader.Authorization];