I'm trying to design a solution where a ServiceStack server can just use an authentication cookie from ASP.NET. (In reality, it could be any cookie. It's just getting a session ID that it can lookup details using a back channel). The custom auth providers don't seem to be the right direction since they are based on credentials being sent. Instead, a GlobalRequestFilter made more sense to me. In there, I check the cookie, get the external session information, then set them to the ServiceStack session and set IsAuthenticated. This works fine in the request service as it has access to the session details that it needs. Fine so far.
The issue, is that when I decide to lock down services with the Authenticate attribute, it apparently runs the attribute prior to my filter so it always wants to redirect them to login. What is the recommended place to add my logic so it fires before the Authenticate attribute and validates properly?
ServiceStack's [Autenticate] attribute is for use with ServiceStack's AuthProvider model so you'll still want to use a Custom AuthProvider. You can have a look at the IAuthWithRequest Auth Providers in the last release notes for examples of creating Custom Auth Providers that aren't based on using credentials:
JwtAuthProviderReader.cs
ApiKeyAuthProvider.cs
AspNetWindowsAuthProvider.cs
By implementing IAuthWithRequest interface in your AuthProvider the [Authenticate] Request Filter will call PreAuthenticate() to perform any Auth validation before validating whether the User is Authenticated or not. Here you can populate the Users Session if the User is Authenticated, e.g:
public class MyAuthProvider : AuthProvider, IAuthWithRequest
{
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
return session.IsAuthenticated;
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
throw new NotImplementedException("Authenticate() should not be called directly");
}
public void PreAuthenticate(IRequest req, IResponse res)
{
//Do any Auth validation...
//populate the Session in the Request to Authenticate this user
req.Items[Keywords.Session] = new AuthUserSession {
UserName = ...,
Email = ...,
//populate other fields
IsAuthenticated = true,
};
}
}
Then to register your custom Auth Provider add it to your AuthFeature plugin in AppHost.Configure(), e.g:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new MyAuthProvider (),
}));
Related
I am using Passport to log in users to a Laravel API endpoint, users get authenticated using their social accounts (google, facebook) using laravel-socialite package.
the workflow of logging users in and out works perfectly (generating tokens...Etc). The problem is I have a controller that should return data based on whether there is a user logged in or not.
I do intercept the Bearer token from the HTTP request but I couldn't get the user using the token (I would use DB facade to select the user based on the token but I am actually looking whether there is a more clean way already implemented in Passport)
I also don't want to use auth:api middleware as the controller should work and return data even if no user is logged in.
this is the api route:
Route::get("/articles/{tag?}", "ArticleController#get_tagged");
this is the logic I want the controller to have
public function get_tagged($tag = "", Request $request)
{
if ($request->header("Authorization"))
// return data related to the user
else
// return general data
}
Assuming that you set your api guard to passport, you can simply call if (Auth::guard('api')->check()) to check for an authenticated user:
public function get_tagged($tag = "", Request $request)
{
if (Auth::guard('api')->check()) {
// Here you have access to $request->user() method that
// contains the model of the currently authenticated user.
//
// Note that this method should only work if you call it
// after an Auth::check(), because the user is set in the
// request object by the auth component after a successful
// authentication check/retrival
return response()->json($request->user());
}
// alternative method
if (($user = Auth::user()) !== null) {
// Here you have your authenticated user model
return response()->json($user);
}
// return general data
return response('Unauthenticated user');
}
This would trigger the Laravel authentication checks in the same way as auth:api guard, but won't redirect the user away. In fact, the redirection is done by the Authenticate middleware (stored in vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php) upon the failure of the authentication checking.
Beware that if you don't specify the guard to use, Laravel will use the default guard setting in the config/auth.php file (usually set to web on a fresh Laravel installation).
If you prefer to stick with the Auth facade/class you can as well use Auth::guard('api')->user() instead or the request object.
thanks to #mdexp answer
In my case I can resolve my problem with using
if (Auth::guard('api')->check()) {
$user = Auth::guard('api')->user();
}
In my controller.
My ASPNetCore web API is using JWT (Json Web Tokens) for authentication. The JWT token has an external and internal user ID inside it. Having these ID's in the JWT does not concern me, as JWT's can't be tampered with or they become invalid, and the internal ID is not useful anywhere outside the system. Of course, the password is not in the JWT content.
Within the JWT, the external user ID becomes the user's System.Security.Claims.ClaimType.Name. The internal ID is set as a JwtRegisteredClaimName.UniqueName value.
When calls are made to the web API, it is good that the [Authorize] attribute attribute makes sure that the user has authenticated and has a currently valid JWT. The concern I have is that once the user is logged in, there is an opportunity for hacking by using the Web API, sending external or internal user id's as criteria that do not match the currently authenticated user. Some web methods in the controllers accept the internal user ID as part of the request being posted, for example, a call to save user information has the internal user ID inside, used as the key for saving the data. I need to be sure that the authenticated user matches/is the same as the user whose data is being saved via the Web API.
My Question is how and where to best implement this data-level security in my web api? Policies don't seem like they can be applied against the data being passed. Authorization filters don't seem to have access to the message body nor any data bindings. Action filters (Microsoft.ASPNetCore.MVC.Filters) run later, but seem like they may not really be intended for this. Also, how do you access the body of the message that was posted inside an action filter? Or should I always make sure that the user ID is passed to methods as a consistently named parameter that I can access via ActionExecutingContext.ActionArguments?
I've searched many posts and not found any scenarios that match what I'm trying to do.
You can always use Middleware to intercept the call when the Response object has been populated , see code sample form here and here .
Authorization filters could also read the request body with EnableRewind :
public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var request = context.HttpContext.Request;
context.HttpContext.Request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
var requestBody = stream.ReadToEnd();
}
}
}
Also works in action filters :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ReadableBodyStreamAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var request = actionContext.HttpContext.Request;
var route = request.Path.HasValue ? request.Path.Value : "";
var requestHeader = request.Headers.Aggregate("", (current, header) => current + $"{header.Key}: {header.Value}{Environment.NewLine}");
var requestBody = "";
request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
requestBody = stream.ReadToEnd();
}
if (...)
{
var wrongResult = new { error = "Wrong parameters" };
actionContext.Result = new JsonResult(wrongResult);
}
}
}
This is all new to me and I'm still trying to wrap my head around it. I've got an IDP (Identity Server 4) set up, and I was able to configure a client to authenticate to it (Angular 6 App), and further more to authenticate to an API (Asp.Net Core 2.0). It all seems to work fine.
Here's the client definition in the IDP:
new Client
{
ClientId = "ZooClient",
ClientName = "Zoo Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = true,
RedirectUris = { "http://localhost:4200/home" },
PostLogoutRedirectUris = { "http://localhost:4200/home" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Address,
"roles",
"ZooWebAPI"
}
}
I'm requesting the following scopes in the client:
'openid profile email roles ZooWebAPI'
The WebAPI is set up as such:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonFormatters()
.AddAuthorization();
services.AddCors();
services.AddDistributedMemoryCache();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:44317";
options.RequireHttpsMetadata = false;
options.ApiName = "ZooWebAPI";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(policy =>
{
policy.WithOrigins("http://localhost:4200");
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.AllowCredentials();
policy.WithExposedHeaders("WWW-Authenticate");
});
app.UseAuthentication();
app.UseMvc();
}
By using [Authorize] I was successfully able to secure the API:
[Route("api/[controller]")]
[Authorize]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public ActionResult Get()
{
return new JsonResult(User.Claims.Select(
c => new { c.Type, c.Value }));
}
}
Everything works fine, if client is not authenticated, browser goes to IDP, requires authentication, redirects back with access token, access token is then used for API calls that are successfully made.
If I look at the Claims in the User object, I can see some information, but I don't have any user information. I can see the scopes, and etc, but no roles for example. From what I read, that is to be expected, and the API should not care about what user is calling it, but how would I go by restricting API calls based on roles? Or would that be completely against specs?
The IDP has an userinfo end point that returns all the user information, and I thought that would be used in the WebAPI, but again, from some reading, it looks like the intention is for that end point to be called from the client only.
Anyway, I would like to restrict Web API calls based on the roles for a specific user. Does anyone have any suggestions, comments? Also, I would like to know what user is making the call, how would I go by doing that?
JWT example:
Thanks
From what I can learn from your information, I can tell the following.
You are logging in through an external provider: Windows Authentication.
You are defining some scopes to pass something to the token that indicates access to specific resources.
The User object you speak of, is the User class that gets filled in from the access token. Since the access token by default doesn't include user profile claims, you don't have them on the User object. This is different from using Windows Authentication directly where the username is provided on the User Principle.
You need to take additional action to provide authorization based on the user logging in.
There a couple of points where you can add authorization logic:
You could define claims on the custom scopes you define in the configuration of Identityserver. This is not desirable IMHO because it's fixed to the login method and not the user logging in.
You could use ClaimsTransformation ( see links below). This allows you to add claims to the list of claims availible at the start of your methods. This has the drawback ( for some people an positive) that those extra claims are not added to the access token itself, it's only on your back-end where the token is evaluated that these claims will be added before the request is handled by your code.
How you retrieve those claims is up to your bussiness requirements.
If you need to have the user information, you have to call the userinfo endpoint of Identityserver to know the username at least. That is what that endpoint is intended for. Based on that you can then use your own logic to determine the 'Roles' this user has.
For instance we created an separate service that can configure and return 'Roles' claims based upon the user and the scopes included in the accesstoken.
UseClaimsTransformation .NET Core
UseClaimsTransformation .NET Full framework
I am using Owin middleware to implement token-based security for my application. When issuing the access token to the client I would also like to pass User Id along with the token, so that, the client application will know the User Id and will be able to call GetUserById (one of the methods inside UserController) in order to show the user his starting page. The best solution I could come up with so far is just adding User Id to the response header. Take a look at my OAuthAuthorizationServerProvider class, in GrantResourceOwnerCredentialsmethod I am adding User Id to the header, using context.Response.Headers.Add("User-Id", new string[] { "1" })
Here is the implementation of my OAuthAuthorizationServerProviderclass
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
//The actual credential check will be added later
if (context.UserName=="user" && context.Password=="user")
{
identity.AddClaim(new Claim("Id", "1"));
context.Validated(identity);
//Here I am adding User Id to the response header
context.Response.Headers.Add("User-Id", new string[] { "1" });
}
else
{
context.SetError("invalid_grant","The credentials provided are not valid");
return;
}
}
}
Basically the client then will have to read User-Id from the header. Is this a good solution I came up with or there is a better one? Also what if I want to pass the whole User object with all its properties to the response is it possible and how to do this?
Since you store the ID already in the claims, why don't you just decode your token on the client and read out the user id like that? Passing it through the headers could allow tampering with it (security).
Have a look on how you could achieve to decode your token and read the claims. This is a c# example https://contos.io/peeking-inside-your-jwt-tokens-using-c-bf6a729d06c8 but this could also be done even through javascript.
This, assuming you use the JWT-format as token (was not specified in your initial question).
Bad way to store UserID as a response header back to client side. This is a huge security concern.
The best way would be to store it as a Claims.
It is very easy to achieve this and get back the claims in the client side.
In your controller, call this and remember to reference
using Microsoft.AspNet.Identity;
var userId = User.Identity.GetUserId();
I am working with a remote API that is normally accessed directly via JavaScript. In the normal flow, The user authenticates by sending Auth headers and in return is granted a cookie.
What I am trying to do is send auth headers from a laravel app, authenticate in the app controller, and provide API access through laravel controller functions.
I was hoping this would be as simple as authenticating and sending my subsequent API calls, hoping that the cookie given to the PHP server would continue to grant authentication.
Well that doesn't work and thats fine, but now I am thinking that I need to store my access cookie in the Session, and send it in the headers for future API calls.
Will this work/how can I go about this? My supervisors don't want to implement OAuth type tokens on the remote server and to me that seems like the best route, so I am a bit stuck.
Cookies cannot be shared across multiple hosts. The cookie (on the client) is only valid for path which set it.
EDIT - ADDING ADDITION AUTH DETAIL
Setting up remember me in Laravel
When migrating (creating) you User table add $table->rememberToken()
to create that column in your User table.
When user signs up to your service add a check box to allow them to
make the decision OR you can just set it true if you don’t to offer
the user the option as described in step 3
< input type="checkbox" name="remember" >
In your controller you add the following code:
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}
Users table must include the string remember_token column per 1. , now assuming you have added the token column to your User table you can pass a boolean value as the second argument to the attempt method, which will keep the user authenticated indefinitely, or until they manually logout. i.e. Auth::attempt([$creditentials], true);
Side note: the Illuminate\Contracts\Auth\UserProvider contract, public function updateRememberToken(Authenticatable $user, $token) uses the user’s UID and token stored in the User table to store the session auth.
AUTH ONCE:
Laravel has once method to log a user into the application for a single request. No sessions or cookies. Used with stateless API.
if (Auth::once($credentials)) {
//
}
OTHER NOTES
The remember cookie doesn't get unset automatically when user logs out. However using the cookie as I explained below in cookies example you could add this to your logout function in your controller just before you return the redirect response after logout.
public function logout() {
// your logout code e.g. notfications, DB updates, etc
// Get remember_me cookie name
$rememberCookie = Auth::getRecallerName();
// Forget the cookie
$forgetCookie = Cookie::forget($rememberCookie);
// return response (in the case of json / JS) or redirect below will work
return Redirect::to('/')->withCookie($forgetCookie);
OR you could q$ueue it up for later if you are elsewhere and cannot return a response immediately
Cookie::queue(forgetCookie);
}
Basic general cookie example that might help you. There are better approaches to do this using a Laravel Service provider
// cookie key
private $myCookieKey = 'myAppCookie';
// example of cookie value but can be any string
private $cookieValue = 'myCompany';
// inside of a controller or a protected abstract class in Controller,
// or setup in a service ... etc.
protected function cookieExample(Request $request)
{
// return true if cookie key
if ($request->has($this->myCookieKey)) {
$valueInsideOfCookie = Cookie::get($this->myCookieKey);
// do something with $valueInsideOfCookie
} else {
// queue a cookie with the next response
Cookie::queue($this->myCookieKey, $this->cookieValue);
}
}
public function exampleControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function one code
}
public function secondControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function two code
}