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

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>()

Related

How to make an authorization and access token requests with PKCE

Can you point me to the right way and set of parameters to:
Make an authorization request with PKCE to my identity endpoint (https://.../login) in Postman
In the attachments there is the list of parameters that send.
as grant_type value I use --> authorization_code
Unfortunately I get "bad request", Invalid_grant in Postman
make the access token request. In this request I get Invalid request. I guess I miss the parameter refresh token but I don't know how to get/generate it:
I wrote the code of the Azure function to request the Access Token, unfortunately I get {"error":"invalid_request"} from the token endpoint.
Here is my code, can you tell me what I am doing wrong?
[FunctionName("GetAccessToken")]
public async Task<IActionResult> GetAccessToken(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function ''GetAccessToken'' processed a request.");
string clientSecret = "some secret";
string accessToken = "";
RequestAccessToken rT = new RequestAccessToken();
rT.Code = req.Form["code"];
rT.RedirectUri = req.Form["redirect_uri"];
rT.GrantType = req.Form["grant_type"];
rT.ClientId = req.Form["client_id"];
rT.CodeVerifier = req.Form["code_verifier"];
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://<access_token_endpoint_base_uri>");
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));//ACCEPT header
var body = new { EntityState = new {
code = rT.Code,
redirect_uri = rT.RedirectUri,
grant_type = rT.GrantType,
client_id = rT.ClientId,
client_secret = clientSecret,
code_verifier = rT.CodeVerifier
} };
var result = await client.PostAsJsonAsync(
"/login",
body);
accessToken = await result.Content.ReadAsStringAsync();
}
return new OkObjectResult(accessToken);
}
catch (Exception ex)
{
log.LogInformation(ex.ToString());
return new ObjectResult(ex.ToString()) { StatusCode = 500 };
}
}
Steps 4 and 8 of my blog post show the standard PKCE parameters.
It is tricky to reproduce the whole flow via a tool such as Postman though, since there is typically also the need to follow redirects and manage a form post of user + password.

Where to store JWT Token in .net core web api?

I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()

Azure mobile apps Custom + Facebook authentication with Xamarin.Forms

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.
Server project:
Auth controller
public class ClubrAuthController : ApiController
{
private readonly ClubrContext dbContext;
private readonly ILoggerService loggerService;
public ClubrAuthController(ILoggerService loggerService)
{
this.loggerService = loggerService;
dbContext = new ClubrContext();
}
public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
{
var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
if (user == null)
{
user = await CreateUser(loginRequest);
}
var token = GetAuthenticationTokenForUser(user.Email);
return Ok(new
{
authenticationToken = token.RawData,
user = new { userId = loginRequest.username }
});
}
private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userEmail)
};
var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var audience = Identifiers.Environment.ApiUrl;
var issuer = Identifiers.Environment.ApiUrl;
var token = AppServiceLoginHandler.CreateToken(
claims,
secretKey,
audience,
issuer,
TimeSpan.FromHours(24)
);
return token;
}
}
Startup.cs
ConfigureMobileAppAuth(app, config, container);
app.UseWebApi(config);
}
private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Client project:
MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);
Additionally I configured Facebook provider in azure portal like described here.
But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs.
What I am missing to make both types of authentication works at the same time?
Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).
app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.
One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.
If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.
The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.
For other folks that find this issue
The documentation for custom authentication can be found here.
A general overview of App Service Authentication / Authorization can be found here.
The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).
Check out Chapter 2 of my book - http://aka.ms/zumobook

Identity Server 3. How can I redirect to a specific uri after call method SignIn?

My code in ASP.NET MVC:
var context = Request.GetOwinContext();
context.Authentication.SignOut();
context.Authentication.SignIn(new AuthenticationProperties() { RedirectUri = ConfigurationSettings.AppSettings["SiteUri"] + "Callback" }, (ClaimsIdentity)CurrentUser.Identity);
My code in IdentityServer Client.cs:
RedirectUris = new List<string>
{
ConfigurationManager.AppSettings["SiteUri"],
ConfigurationManager.AppSettings["SiteUri"]+"Callback"
}
I'm expecting a call CallbackController , but this is not happening. What could be the reason??
RedirectUri is an endpoint that will be called after successful authentication of user on identity server login page in implicit flow.
Since there is no information about method where code for SignIn is placed, i can just guessing that something like this will help:
await SignInMethod(HttpContext.GetOwinContext().Authentication, ....);
return new RedirectResult(string.Format("{0}{1}", ConfigurationManager.AppSettings["SiteUri"], "callback"));
}

Attempt to reuse auth token to connect xamarin app to azure fails

This initial login succeeds:
public static MobileServiceClient MOBILE = new MobileServiceClient("https://myapp.azure-mobile.net/",myApplicationKey);
MobileServiceAuthenticationProvider GOOGLEPROVIDER = MobileServiceAuthenticationProvider.Google;
private async Task Connect() {
var USER = await MOBILE.LoginAsync(this, GOOGLEPROVIDER);
var CACHE = new Dictionary<string, string> { { "token", USER.MobileServiceAuthenticationToken } };
var ACCOUNT = new Account(USER.UserId, CACHE);
var STORE = AccountStore.Create(this);
STORE.Save(ACCOUNT, "Google");
}
but then this attempt to reuse the token to reconnect without a login page fails:
public async Task Reconnect() {
var STORE = AccountStore.Create(this);
var token = STORE.FindAccountsForService("Google").ToArray()[0].Properties["token"];
// token seems ok
var jsonToken = new JObject();
jsonToken.Add("access_token", token);
var USER = await MOBILE.LoginAsync(MobileServiceAuthenticationProvider.Google, jsonToken); // BOOM!
}
... with the following message: "The POST Google login request must contain both code and id_token in the body of the request."
What I am getting wrong here?
The token you use in the code, viz.
var CACHE = new Dictionary { { "token",USER.MobileServiceAuthenticationToken } };
The MobileServiceAuthenticationToken above is a token specific to MobileServices and cannot be used in the LoginAsync method (LoginAsync method requires a Google OAuth token.)
Please see this Get User Info from Google Api Using Azure Mobile Services for Xamarin Android