I am using Swashbuckle.AspNetCore 5.0.0 to generate Swagger documentation for my .Net Core WebApi project, and for the most part, everything is going fine.
I have set up some simple authentication using ApiKey, and that is working good.
Where I am having problems now is getting Swagger to add an ApiKey into the header of my requests. I followed the instructions for added the ApiKey security Definition/requirement, as mentioned in these various posts:
API key in header with swashbuckle
Empty authorization header on requests for Swashbuckle.AspNetCore
How to force Swagger/Swashbuckle to append an API key?
However, the ApiKey value is never added to the Header.
This is what I have in my startup:
c.AddSecurityDefinition("ApiKey",
new OpenApiSecurityScheme
{
Description = "ApiKey must appear in header",
Type = SecuritySchemeType.ApiKey,
Name = Constants.ApiKeyHeaderName,
In = ParameterLocation.Header
});
and
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Name = Constants.ApiKeyHeaderName,
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header
},
new List<string>()}
});
I was struggling myslef with this one but figured out that besides adding proper Reference, you have to also specify Scheme in definition, this is the code that is working for me correctly:
c.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme()
{
Name = "x-api-key",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Description = "Authorization by x-api-key inside request's header",
Scheme = "ApiKeyScheme"
});
var key = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "ApiKey"
},
In = ParameterLocation.Header
};
var requirement = new OpenApiSecurityRequirement
{
{ key, new List<string>() }
};
c.AddSecurityRequirement(requirement);
Important tip is name in AddSecurityDefinition must be the same as Id in OpenApiReference. name can be every string.
OK, I was finally able to get this to work. I needed to add an instance of OpenApiReference to the OpenApiSecurityScheme object provided to c.AddSecurityRequirement()
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "ApiKeyAuth" }
I have to say that the documentation on this is a bit confusing. Probably not in small part due to the fact that anything posted on the internet is there forever, and so many posts that I found on this whole thing were no longer applicable due to changes in the framework :)
Now I just need to figure out how to send another header value along with the api-key, and I'll be done with this part
Related
In OData when an entity had a media type, we should return along with the entity:
"odata.mediaReadLink": "Employees(1)/$value"
"odata.mediaContentType": "image/jpeg",
ref: http://docs.oasis-open.org/odata/odata-json-format/v4.0/cs01/odata-json-format-v4.0-cs01.html
I am using aspnet core and OData, but I cannot find the way to do this, well, at least not simply as I would expect this to be.
Below I show how I create the model builder, I add media type to the entity Assessment and then I try to specify the HasReadLink, but when I get an entity by Id, I do not receive back the odata media read link.
var builder = new ODataConventionModelBuilder(serviceProvider);
//This adds HasStream true in the metadata
var assessmentEntityType = builder.EntityType<Assessment>();
assessmentEntityType.MediaType();
var assessmentEntitySetConfiguration = builder.EntitySet<Assessment>(nameof(Assessment) + "s");
// Does not produce effects getting the entity by id
// UPDATE: this code below does have effect only if i set false in the second
// parameter, but anyway this has nothing to do with the media read link
assessmentEntitySetConfiguration.HasReadLink(c =>
{
return new Uri("/not-shown");
}, true);
// see NavigationSourceLinkBuilderAnnotation
// https://github.com/OData/WebApi/blob/d02bc61ea7b31ada1e54abbeebbecb3c5df0e3ac/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceLinkBuilderAnnotation.cs
assessmentEntitySetConfiguration.EntityType
.Filter(QueryOptionSetting.Allowed, nameof(Assessment.IsDeleted))
.Filter(QueryOptionSetting.Disabled, nameof(Assessment.Description));
What am I missing?
I could not find an online example to do this in aspnet core, I found some old example to do it in net framework but it is hacky.
UPDATE 1:
In the OData for .net Framework I used SetHasDefaultStream to achieve this.
example:
var model = modelBuilder.GetEdmModel();
var answerTypeName = typeof(Answer).FullName;
var answerType = (IEdmEntityType) model.FindDeclaredType(answerTypeName);
model.SetHasDefaultStream(answerType, true);
In the new core I don't have an option to set the default stream.
I asked the same on OData.net Github project: https://github.com/OData/odata.net/issues/1555
I am using the LinkGenerator in my EmailSender Service inside an ASP.NET Core 2.2 MVC project. It was first returning Null values until I figured out that I needed to include the area in the definition. However, the Url still doesn't provide the server information that I was expecting.
It generates the Url looks like this: "/Identity/Account/Login..."
I expected this: "https://{hostName}/Identity/Account/Login..."
My work around is to concatenate values from the httpContextAccessor as posted. But, this seems hokey. Can someone give me direction as how this is supposed to work?
Work around (not great):
var callbackUrl = $"{httpContextAccessor.HttpContext.Request.Scheme}://" +
$"{httpContextAccessor.HttpContext.Request.Host}" +
linkGenerator.GetPathByPage(httpContextAccessor.HttpContext,
"/Account/Login", null, new {area = "Identity", userId = user.Id});
By design linkGenerator.GetPathByPage will return the relative path.If you need the full Uri including Scheme and Host then you have to use another method GetUriByPage also provided by LinkGenerator as follows:
var callbackUrl = linkGenerator.GetUriByPage(httpContextAccessor.HttpContext,
"/Account/Login", null, new {area = "Identity", userId = user.Id});
I use Xamarin.Auth to authenticate with the OneDrive Service. This worked fine for a while now, but I seems there where changes on the service so it stopped working..
I upgraded to the new version 2.0 and try to make it work again. The Initial authentication works well so far. But after a while it always started to crash. I realized that there isn't any refrehs token sent back from the onedrive service.
This is the code to call the Auth UI:
private Task<IDictionary<string, string>> ShowWebView()
{
var tcs = new TaskCompletionSource<IDictionary<string, string>>();
var auth = new OAuth2Authenticator(ServiceConstants.MSA_CLIENT_ID,
string.Join(",", ServiceConstants.Scopes),
new Uri(GetAuthorizeUrl()),
new Uri(ServiceConstants.RETURN_URL));
auth.Completed +=
(sender, eventArgs) =>
{
tcs.SetResult(eventArgs.IsAuthenticated ? eventArgs.Account.Properties : null);
};
var intent = auth.GetUI(Application.Context);
intent.SetFlags(ActivityFlags.NewTask);
Application.Context.StartActivity(intent);
return tcs.Task;
}
private string GetAuthorizeUrl()
{
var requestUriStringBuilder = new StringBuilder();
requestUriStringBuilder.Append(ServiceConstants.AUTHENTICATION_URL);
requestUriStringBuilder.AppendFormat("?{0}={1}", ServiceConstants.REDIRECT_URI,
ServiceConstants.RETURN_URL);
requestUriStringBuilder.AppendFormat("&{0}={1}", ServiceConstants.CLIENT_ID,
ServiceConstants.MSA_CLIENT_ID);
requestUriStringBuilder.AppendFormat("&{0}={1}", ServiceConstants.SCOPE,
WebUtility.UrlEncode(string.Join(" ", ServiceConstants.Scopes)));
requestUriStringBuilder.AppendFormat("&{0}={1}", ServiceConstants.RESPONSE_TYPE, ServiceConstants.CODE);
return requestUriStringBuilder.ToString();
}
The Authorize URI is:
https://login.live.com/oauth20_authorize.srf?redirect_uri=https://login.live.com/oauth20_desktop.srf&client_id=["id"]&scope=onedrive.readwrite+wl.offline_access+wl.signin&response_type=code
The response I get contains 6 Elements:
access_token: "EwAIA..."
token_type: "bearer"
expires_in: "3600"
scope: "onedrive.readwrite wl.offline_access wl.signin wl.basic wl.skydrive wl.skydrive_update onedrive.readonly"
user_id: "41...."
state: "ykjfmttehzjebqtp"
When I check it with the Documentation (https://dev.onedrive.com/auth/msa_oauth.htm) I can't see what's wrong here. Any ideas?
I called wrong constructor. This one works:
authenticator = new OAuth2Authenticator(ServiceConstants.MSA_CLIENT_ID,
ServiceConstants.MSA_CLIENT_SECRET,
string.Join(",", ServiceConstants.Scopes),
new Uri(ServiceConstants.AUTHENTICATION_URL),
new Uri(ServiceConstants.RETURN_URL),
new Uri(ServiceConstants.TOKEN_URL));
With these constants:
Scopes = {"onedrive.readwrite", "wl.offline_access", "wl.signin"};
RETURN_URL = "https://login.live.com/oauth20_desktop.srf";
AUTHENTICATION_URL = "https://login.live.com/oauth20_authorize.srf";
TOKEN_URL = "https://login.live.com/oauth20_token.srf";
I have an OData (v3) Web API 2 project that is a wrapper to another wcf web service. The intended client for this odata connection is SharePoint 2013. I am creating CRUD operations within this wrapper and noticed that when sharepoint is asked to delete something it send a request in this format: /Entity(Identity=XX) instead of it's normal /Entity(XX) that i have working normally. I need to be able to handle that request without breaking the other one. Here is my code:
public IHttpActionResult GetSchool([FromODataUri] int key, ODataQueryOptions<School> queryOptions)
{
// validate the query.
try
{
queryOptions.Validate(_validationSettings);
}
catch (ODataException ex)
{
return BadRequest(ex.Message);
}
SchoolDataSource data = new SchoolDataSource();
var result = data.GetByID(key);
return Ok<School>(result);
//return StatusCode(HttpStatusCode.NotImplemented);
}
This works fine for a request for /Schools(1), but not for /Schools(ID=1). i have tried adding:
[Route("Schools(ID={key}")]
And this makes the /Schools(ID=1) route work, but breaks pretty much everything else (406 Errors). i tried adding the above attribute and
[Route("Schools({key})")]to see if i can get them both working, but it doesn't function correctly either. I am very new to this, and was hoping to at least get some direction. Here is my WebApiConfig:
config.MapHttpAttributeRoutes();
config.EnableQuerySupport();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<School>("Schools");
builder.DataServiceVersion = new Version("2.0");
config.Routes.MapODataRoute("odata", null, builder.GetEdmModel());
// Web API routes
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Errors i get:
406 if i have the route attribute set. 500 if i dont have the route attribute set. it seems as though my service has no idea how to handle the parameter unless i specify it, but if i do, all calls get 406 errors.
may not be the best approach, but made it work with this class:
public class SharePointRoutingConvention : EntitySetRoutingConvention
{
public override string SelectAction(ODataPath odataPath, HttpControllerContext context,
ILookup<string, HttpActionDescriptor> actionMap)
{
//Gets the entity type
IEdmEntityType entityType = odataPath.EdmType as IEdmEntityType;
//makes sure the format is correct
if (odataPath.PathTemplate == "~/entityset/key")
{
//parses out the path segment (Identity=X)
KeyValuePathSegment segment = odataPath.Segments[1] as KeyValuePathSegment;
//Gets the verb from the request header
string actionName = context.Request.Method.ToString();
// Add keys to route data, so they will bind to action parameters.
KeyValuePathSegment keyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
//Checks to see if the "Identity=" part is in the url
if (keyValueSegment.Value.Contains("Identity="))
{
//removes the extra text
context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value.Replace("Identity=", "");
}
else
{
//parses it normally
context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value;
}
//returns the verb
return actionName;
}
// Not a match.
return null;
}
}
and make the change to the webapiconfig:
var conventions = ODataRoutingConventions.CreateDefault();
//adding the custom odata routing convention
conventions.Insert(0, new SharePointRoutingConvention());
config.Routes.MapODataRoute(
routeName: "odata",
routePrefix: null,//this is so that you can type the base url and get metadata back (http://localhost/)
model: builder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: conventions //this assigns the conventions to the route
);
I have a question about this create/Update leads API, http://developers.marketo.com/documentation/rest/createupdate-leads/.
There is no sample code for C# or JAVA. Only ruby available. So I have to try it by myself. But I always get null return from the response.
Here is my code:
private async Task<CreateLeadResponseResult> CreateLead(string token)
{
string url = String.Format(marketoInstanceAddress+"/rest/v1/leads.json?access_token={0}", token);
var fullUri = new Uri(url, UriKind.Absolute);
CreateLeadResponseResult createLeadResponse = new CreateLeadResponseResult();
CreateLeadInput input = new CreateLeadInput { email = "123#123.com", lastName = "Lee", firstName = "testtesttest", postCode = "00000" };
CreateLeadInput input2 = new CreateLeadInput { email = "321#gagaga.com", lastName = "Lio", firstName = "ttttttt", postCode = "00000" };
List<CreateLeadInput> inputList = new List<CreateLeadInput>();
inputList.Add(input);
inputList.Add(input2);
CreateLeadRequest createLeadRequest = new CreateLeadRequest() { input = inputList };
JavaScriptSerializer createJsonString = new JavaScriptSerializer();
string inputJsonString = createJsonString.Serialize(createLeadRequest);
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsJsonAsync(fullUri.OriginalString, inputJsonString).ConfigureAwait(false);
// I can see the JSON string is in the message body in debugging mode.
if (response.IsSuccessStatusCode)
{
createLeadResponse = await response.Content.ReadAsAsync<CreateLeadResponseResult>();
}
else
{
if (response.StatusCode == HttpStatusCode.Forbidden)
throw new AuthenticationException("Invalid username/password combination.");
else
throw new ApplicationException("Not able to get token");
}
}
return createLeadResponse;}
//get null here.
Thank you.
-C.
The best way to debug this is to capture the exact URL, parameters and JSON that are submitted by your app and try submitting those manually via a tool like Postman (Chrome plug-in) or SOAP UI. Then you see the exact error message, which you can look up here: http://developers.marketo.com/documentation/rest/error-codes/. Based on that you can update your code. I don't know much about Java, but this is how I got my Python code to work.
Your example code was really helpful in getting my own implementation off the ground. Thanks!
After playing with it for a bit, I realized that the JavaScriptSerializer step is unnecessary since PostAsJsonAsync automatically serializes whatever object you pass to it. The double serialization prevents Marketo's API from processing the input.
Also, I agree with Jep that Postman is super helpful. But in the case of this error, Postman was working fine (using the contents of inputJsonString) but my C# code still didn't work properly. So I temporarily modified the code to return a dynamic object instead of a CreateLeadResponseResult. In debugging mode this allowed me to see fields that were discarded because they didn't fit the CreateLeadResponseResult type, which led me to the solution above.