ServiceStack - customize auth response - authentication

ServiceStack - customize auth response

Both the AuthenticateResponse and ResponseStatus DTO's contain a Meta string Dictionary which you can use to add additional data to.
You can modify the returned AuthenticateResponse DTO with a Response Filter or overriding the OnAfterExecute() method in your AppHost.
Using a Global ResponseFilter
this.GlobalResponseFilters.Add((req, res, responseDto) => {
var authResponse = responseDto as AuthenticateResponse;
if (authResponse != null) {
authResponse.Meta = new Dictionary<string,string> {
{"foo", "bar"}
};
}
});

Related

Flutter Chopper 401 renew & retry interceptor

I'm using Chopper in my flutter app and what I need to do is, when I get 401 response status code (unauthorized) from my API, I must call another endpoint that will refresh my token and save it into secured storage, when all of this is done, I need to retry the request instantly (so that user cannot notice that his token expired). Is this dooable with Chopper only, or I have to use some other package?
It is possible. You need to use the authenticator field on the Chopper client, e.g.
final ChopperClient client = ChopperClient(
baseUrl: backendUrl,
interceptors: [HeaderInterceptor()],
services: <ChopperService>[
_$UserApiService(),
],
converter: converter,
authenticator: MyAuthenticator(),
);
And your authenticator class, should look something like this:
class MyAuthenticator extends Authenticator {
#override
FutureOr<Request?> authenticate(
Request request, Response<dynamic> response) async {
if (response.statusCode == 401) {
String? newToken = await refreshToken();
final Map<String, String> updatedHeaders =
Map<String, String>.of(request.headers);
if (newToken != null) {
newToken = 'Bearer $newToken';
updatedHeaders.update('Authorization', (String _) => newToken!,
ifAbsent: () => newToken!);
return request.copyWith(headers: updatedHeaders);
}
}
return null;
}
Admittedly, it wasn't that easy to find/understand (though it is the first property of the chopper client mentioned in their docs), but it is precisely what this property is for. I was going to move to dio myself, but I still had the same issue with type conversion on a retry.
EDIT: You will probably want to keep a retry count somewhere so you don't end up in a loop.
I searched couple of days for answer, and I came to conclusion that this is not possible with Chopper... Meanwhile I switched to Dio as my Networking client, but I used Chopper for generation of functions/endpoints.
Here is my Authenticator. FYI I'm storing auth-token and refresh-token in preferences.
class AppAuthenticator extends Authenticator {
#override
FutureOr<Request?> authenticate(Request request, Response response, [Request? originalRequest]) async {
if (response.statusCode == HttpStatus.unauthorized) {
final client = CustomChopperClient.createChopperClient();
AuthorizationApiService authApi = client.getService<AuthorizationApiService>();
String refreshTokenValue = await Prefs.refreshToken;
Map<String, String> refreshToken = {'refresh_token': refreshTokenValue};
var tokens = await authApi.refresh(refreshToken);
final theTokens = tokens.body;
if (theTokens != null) {
Prefs.setAccessToken(theTokens.auth_token);
Prefs.setRefreshToken(theTokens.refresh_token);
request.headers.remove('Authorization');
request.headers.putIfAbsent('Authorization', () => 'Bearer ${theTokens.auth_token}');
return request;
}
}
return null;
}
}
Based on this example: github
And Chopper Client:
class CustomChopperClient {
static ChopperClient createChopperClient() {
final client = ChopperClient(
baseUrl: 'https://example.com/api/',
services: <ChopperService>[
AuthorizationApiService.create(),
ProfileApiService.create(),
AccountingApiService.create(), // and others
],
interceptors: [
HttpLoggingInterceptor(),
(Request request) async => request.copyWith(headers: {
'Accept': "application/json",
'Content-type': "application/json",
'locale': await Prefs.locale,
'Authorization': "Bearer ${await Prefs.accessToken}",
}),
],
converter: BuiltValueConverter(errorType: ErrorDetails),
errorConverter: BuiltValueConverter(errorType: ErrorDetails),
authenticator: AppAuthenticator(),
);
return client;
}
}

Extension Grants - Invalid Grant Type Delegation - Identity Server 4 .NET Core 2.2

I am trying to figure out how to implement a delegation grant type in conjunction with client credentials, by following the tutorial from HERE, which is literally one page, since I have and API1 resource calling another API2 resource.
I've implemented the IExtensionGrantValidator and copied the code from the docs using the class name they provided, and added the client with grant type delegation. However, I am not sure where and how to call this method below, at first I was calling it from the client and tried passing the JWT I initially got to call API1 to the DelegateAsync method but I kept getting a bad request
In API 1 you can now construct the HTTP payload yourself, or use the IdentityModel helper library:
public async Task<TokenResponse> DelegateAsync(string userToken)
{
var payload = new
{
token = userToken
};
// create token client
var client = new TokenClient(disco.TokenEndpoint, "api1.client", "secret");
// send custom grant to token endpoint, return response
return await client.RequestCustomGrantAsync("delegation", "api2", payload);
}
So, I tried from API1 requesting a token in a method called GetAPI2Response which attempts to call a method in API2:
[HttpGet]
[Route("getapi2response")]
public async Task<string> GetApi2Response()
{
var client = new HttpClient();
var tokenResponse = await client.RequestTokenAsync(new TokenRequest
{
Address = "http://localhost:5005/connect/token",
GrantType = "delegation",
ClientId = "api1_client",
ClientSecret = "74c4148e-70f4-4fd9-b444-03002b177937",
Parameters = { { "scope", "stateapi" } }
});
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("http://localhost:6050/api/values");
if (!response.IsSuccessStatusCode)
{
Debug.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
return content;
}
return "failed";
}
However, this returns when debugging an invalid grant type. Strangely, I noticed when running IDSRV the code in the IExtensionGrantValidator method does not get hit, until you click the link for the discovery docs then it appears as a grant type
I'm obviously doing something wrong since I am not including the aforementioned DelegateAsync method from the docs, as its not clear to me where it goes.
The docs seem to be a bit outdated. With the actual extension methods there must be something like:
var tokenResponse = await client.RequestTokenAsync(new TokenRequest
{
Address = "http://localhost:5005/connect/token",
GrantType = "delegation",
ClientId = "api1_client",
ClientSecret = "74c4148e-70f4-4fd9-b444-03002b177937",
Parameters = new Dictionary<string, string>{{ "token", userToken }, { "scope", "stateapi" } }
})
you already implemented it, but forgot to add the initial token. When you extract it from the GetApi2Response() it can become your DelegateAsync.
Then your client configuration in Identityserver has to contain the delegation GrantType for the api1_client. Also don't forget the registration:
services.AddIdentityServer().AddExtensionGrantValidator<YourIExtensionGrantValidatorImpl>()

Define a variable in a middleware available to the following ones

I am using asp.net core, and I would like to get several data from the request before I call the full web app.
So I created a middleware to do this. I found a way to check everything I want, but I don't know how to pass a variable to the following middlewares
app.Use(async (context, next) => {
var requestInfo = GetRequestInfo(context.Request);
if(requestInfo == null)
{
context.Response.StatusCode = 404;
return;
}
// How do I make the request info available to the following middlewares ?
await next();
});
app.Run(async (context) =>
{
// var requestInfo = ???
await context.Response.WriteAsync("Hello World! - " + env.EnvironmentName);
});
Is there a good way to pass data from a middleware to others ? (here I use app.Run, but I would like to have all this in MVC)
Beside features, there is another - a simpler in my opinion - solution: HttpContext.Items, as described here. According to the docs, it is especially designed to store data for the scope of a single request.
Your implementation would look like this:
// Set data:
context.Items["RequestInfo"] = requestInfo;
// Read data:
var requestInfo = (RequestInfo)context.Items["RequestInfo"];
I found the solution : the context contains an IFeatureCollection, and it is documented here
We just need to create a class with all the data :
public class RequestInfo
{
public String Info1 { get; set; }
public int Info2 { get; set; }
}
And we add it to the context.Features :
app.Use(async (context, next) => {
RequestInfo requestInfo = GetRequestInfo(context.Request);
if(requestInfo == null)
{
context.Response.StatusCode = 404;
return;
}
// We add it to the Features collection
context.Features.Set(requestInfo)
await next();
});
Now it is available to the others middlewares :
app.Run(async (context) =>
{
var requestInfo = context.Features.Get<RequestInfo>();
});

Custom 404 response model

I want to provide a custom reponse for all 404s on our API. For example:
{
"message": "The requested resource does not exist. Please visit our documentation.."
}
I believe the following result filter works for all cases within the MVC pipeline:
public class NotFoundResultFilter : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var result = context.Result as NotFoundResult;
if (result != null)
{
context.Result = new HttpNotFoundResult(); // My custom 404 result object
}
}
}
But, when a URL requested does not match an action route, the above filter is not hit. How could I best intercept these 404 responses? Would this require middleware?
Yes, you need to use middleware, as filter is only for MVC.
You may, as always, write your own middleware
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 404)
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject("your text"), Encoding.UTF8);
}
});
Or use built-in middlware StatusCodePagesMiddleware, but as you want to handle only one status, this is an extra functionality. This middleware can be used to handle the response status code is between 400 and 600 .You can configure the StatusCodePagesMiddleware adding one of the following line to the Configure method (example from StatusCodePages Sample):
app.UseStatusCodePages(); // There is a default response but any of the following can be used to change the behavior.
// app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
// app.UseStatusCodePages("text/plain", "Response, status code: {0}");
// app.UseStatusCodePagesWithRedirects("~/errors/{0}"); // PathBase relative
// app.UseStatusCodePagesWithRedirects("/base/errors/{0}"); // Absolute
// app.UseStatusCodePages(builder => builder.UseWelcomePage());
// app.UseStatusCodePagesWithReExecute("/errors/{0}");
Try this:
app.Use( async ( context, next ) =>
{
await next();
if ( context.Response is { StatusCode: 404, Body: { Length: 0 }, HasStarted: false } )
{
context.Response.ContentType = "application/problem+json; charset=utf-8";
string jsonString = JsonConvert.SerializeObject(errorDTO);
await context.Response.WriteAsync( jsonString, Encoding.UTF8 );
}
} );

posting object from angular 2 application to Web Api application (getting Unsupported Media Type)

I could use some guidens, sending an object from my angular 2 application to the Web API.
I know how to GET objects from the Web Api, to my angular 2 application, but can't seem to figure out how the post method works or even if I should use the http.post methodd.
My angular 2 application has the following method:
sendUpdatdReservation(updatedReservation: Reservation) {
var result;
var objectToSend = JSON.stringify(updatedReservation);
this.http.post('http://localhost:52262/api/postbookings', objectToSend)
.map((res: Response) => res.json()).subscribe(res => result = res);
console.log(result);
}
The "updatedReservation" is an object, which I convert to JSON.
The Web api can be reached by the following address:
httl://localhost:52262/api/postbookings
Web Api controller:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class PostBookingsController : ApiController
{
[AcceptVerbs()]
public bool ConfirmBooking(Booking booking)
{
return true;
}
}
What I'm trying to do is to send the object, update my database based on the changes values that the object has. Then send back true or false if this is a confirmation or not so I can redirect to confirmation page.
Do any know the unsupported media type error?, is that related to that the object i send is not what the api method expects?
Hope someone can help.
You need to set the Content-Type header when sending the request:
sendUpdatdReservation(updatedReservation: Reservation) {
var result;
var objectToSend = JSON.stringify(updatedReservation);
var headers = new Headers();
headers.append('Content-Type', 'application/json');
this.http.post('http://localhost:52262/api/postbookings', objectToSend, { headers: headers })
.map((res: Response) => res.json()).subscribe(res => {
this.result = res;
console.log(this.result);
});
}
Don't forget to import this class:
import {Http,Headers} from 'angular2/http';