In IdentityServer4 you can specify an extension grand to enable delegated access tokens for users, so if a webservice needs to call another webservice during a request from a user, it can request a new access token from the IdentityServer with a valid scope and audience. My issue is, that this grant is thought to be used "on behalf of the interactive user", which leads to my question:
If my client calls a webservice which needs to call another webservice during the request, how can i create a delegated access token?
I tried to modify the example from the documentation, but the extension grand expects a token from a user, which holds a "sub" claim, and not from a client, which does not hold a "sub" claim.
The client is a daemon application, so it runs fully automated and without any user interaction, and is authenticated with the client credentials flow.
To clarify what i mean:
Here we see an use case, if a user is present:
User accesses a UI
UI redirects the user to the identity server to authenticate (With the Authorization Code Flow + PCKE). The UI gets the access token back
The UI calls an WebApi A with the access token
The WebApi A needs to access the WebApi B, but the access token was meant for WebApi A. Therefore the WebApi A asks the IdentityServer for a delegated access token to access WebApi B.
The IdentityServer provides the delegated access token.
The new access token is passed along to the WebApi B.
Here we see the same use case, but no user is present and a daemon application does the same thing:
The deamon appliaction authenticates against the IdentityServer with the Client Credentails Flow and gets the access token back
The deamon appliaction calls an WebApi A with the access token
The WebApi A needs to access the WebApi B, but the access token was meant for WebApi A. Therefore the WebApi A asks the IdentityServer for a delegated access token to access WebApi B.
How to get the IdentityServer to provide a the delegated access token for the client?
The new access token is passed along to the WebApi B.
For machine to machine (Service to service) communication you typically use the client credientials grant. This allows services to communicate even if no user is present. The userid (Subject) is typically included in the API calls that is protected using this flow.
See this article
The communication between WebApi-A and WebApi-B can be done using client credentials flow and here you don't need to pass any access token from the user. Instead you pass the subjectId(userID) and optionally some additional claims as part of the API calls between A and B. This way is much simpler and the benefit is that A-B can communicate without any user involved (for example to do workflow style communication in the background).
I have found the solution to my issue. You can extend the given example implementation of the delegation grand and extend it in a way, that a delegation token is issued for a client:
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var oldToken = context.Request.Raw.Get("token");
if (string.IsNullOrEmpty(oldToken))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var result = await _validator.ValidateAccessTokenAsync(oldToken);
if (result.IsError)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var sub = result.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (sub != null)
{
// The old token had a user context
context.Result = new GrantValidationResult(sub, GrantType);
}
// The "magic" is the part below
else
{
// The old token had a client context
var client = result.Claims.FirstOrDefault(c => c.Type.Equals("client_id", StringComparison.Ordinal));
if (string.IsNullOrWhiteSpace(client?.Value))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient);
return;
}
context.Request.ClientId = client.Value;
context.Result = new GrantValidationResult(new Dictionary<string, object>());
}
}
Related
Development Information: Azure Registered Mobile Application, Azure hosted API, Visual Studio 2019, mobile application is built in Xamarin Forms targeting .Net-5 and API is built in Asp.Net targeting .Net-5.
Desired Workflow: User does Authorization code flow authentication interactive if AcquireTokenSilently fails, utilizes MS Authenticator. Once authenticated, the user gets an access token for Graph and for the custom hosted API.
What is working: I can do the MFA authentication piece using the MSAL library and an access token is received in the mobile application, but the token is only good for making calls to Graph. I need to be able to call the protected resource (hosted API) as well. My thoughts originally was that the JWT access token would work with the hosted API as well as Graph, but after reading more, I have read that you have to acquire two separate tokens as to prevent issues where two resources may have the same scope(s).
The problem I am facing is that I thought upon Authenticating there would be an easy way to get another resources access token without having to authenticate a second time. Isn't that the point of the OpenID authentication?? My last thought was to do an implicit authentication from the mobile app to the protected API using a client id, client secret, but I don't want to store a client secret in the mobile app for fear it may expose something sensitive to a user. I tried following the documentation of Microsoft and set up specific scopes for the hosted API and registered it in the Azure portal, but this didn't seem to have any affect on the authentication piece. I still have to authenticate against the API which isn't the desired result.
Is it possible to authenticate once for Graph and then knowing that the user authenticated correctly as they now have a valid token for Graph then somehow call this protected API without having to force them to do authentication again for the protected API and if so, how to do this without exposing any sensitive information (client secret) in the mobile application?
CODE
// Microsoft Authentication client for native/mobile apps
public static IPublicClientApplication PCA;
//Setting up the PublicClientApplicationBuilder
//OAuthSettings is a static class with required values (app id, tenant id, etc)
var builder = PublicClientApplicationBuilder
.Create(OAuthSettings.ApplicationId)
.WithTenantId(OAuthSettings.TenantId)
.WithBroker()
.WithRedirectUri(OAuthSettings.RedirectUri);
PCA = builder.Build();
//Scopes being used in initial authentication
//I tried adding to this with a custom scope, I added on the
//protected api and it caused an exception, so I didn't think
//I could use those together with the scopes for Graph
//Custom scope for the protected API is as follows:
//XXX.User.Common (where XXX is our company name)
public const string Scopes = "User.Read MailboxSettings.Read Calendars.ReadWrite";
try
{
var accounts = await PCA.GetAccountsAsync();
var silentAuthResult = await PCA
.AcquireTokenSilent(Scopes.Split(' '), accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException msalEx)
{
var windowLocatorService = DependencyService.Get<IParentWindowLocatorService>();
// Prompt the user to sign-in
var interactiveRequest = PCA.AcquireTokenInteractive(Scopes);
AuthUIParent = windowLocatorService?.GetCurrentParentWindow();
if (AuthUIParent != null)
{
interactiveRequest = interactiveRequest
.WithParentActivityOrWindow(AuthUIParent);
}
var interactiveAuthResult = await interactiveRequest.ExecuteAsync();
var accounts = await PCA.GetAccountsAsync();
//at this point, I have a valid Graph token, but I need to
//get a valid token for the protected API,
//unsure of how to do this last piece
}
Much of this answer is thanks to Gaurav Mantri but he was too modest to accept credit, and he asked me to post an answer.
First thing I needed to change was the flow of the authentication.
Authenticate the user against the protected API, then get the Graph token, as the Graph authentication works well with the AcquireTokenSilent(IEnumerable<string> scopes, IAccount account) method of MSAL library. I was doing this backwards by authenticating against Graph then trying to get the protected API token.
So, the code for authenticating then looks like the following:
public async Task SignIn()
{
try
{
//check for any accounts which are authenticated
var accounts = await PCA.GetAccountsAsync();
//try to Authenticate against the protected API silently
var silentAuthResult = await PCA
.AcquireTokenSilent(new string[] { "api://{your api client id}/.default" }, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException msalEx)
{
// This exception is thrown when an interactive sign-in is required.
var windowLocatorService = DependencyService.Get<IParentWindowLocatorService>();
// Prompt the user to sign-in
var interactiveRequest = PCA.AcquireTokenInteractive(new string[] { "api://{your api client id}/.default" });
AuthUIParent = windowLocatorService?.GetCurrentParentWindow();
if (AuthUIParent != null)
{
interactiveRequest = interactiveRequest
.WithParentActivityOrWindow(AuthUIParent);
}
var interactiveAuthResult = await interactiveRequest.ExecuteAsync();
var accounts = await PCA.GetAccountsAsync();
//Now we can get the Graph token silently
//We now have valid tokens for both Graph and our protected API
var graphtokenresult = await PCA.AcquireTokenSilent(Scopes, accounts.First()).ExecuteAsync();
}
catch (Exception ex)
{
}
}
In the Azure portal there were a few things I needed to ensure were set in the configurations.
Make sure you set some custom scopes for the protected API - quickstart-configure-app-expose-web-apis
(This one isn't documented in the MSDN documentation)
On the same page you can add scopes for the protected API, there is a section called Add a client application, you would think by clicking add and then selecting an application which you want to grant permission to access your API would suffice, but it is not.
You also need to go into the protected API's Manifest and add the client id of the application you want to grant access manually, as clicking the add button and selecting the client application does not modify the manifest. So, open the manifest of your protected API and add the client application id to the section of the manifect labeled knownClientApplications:
Once all of this has been done, you can now receive an access token which will authorize against the protected API as well as a Graph token for getting user information. I hope this is helpful and thanks again to Gaurav Mantri. If anyone has more questions about this, please contact me and I'll do my best to pass on what I have learned.
Is it possible to acquire a jwt token for the current application and not based on another Azure app or keep it through the entire lifecycle of signed in?
In my app I'm authenticating my user to Azure with Owin and OpenIdConnect. Authentication succeeded and I received a token from Azure.
In later methods I need to pass the token as parameter, so now I store it in session variable, but this expires earlier and results in an empty variable.
Now I'm trying to acquire a token from Azure with ADAL.
string userObjectId = ((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(c => c.ToString() == "http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
AuthenticationContext authContext = new AuthenticationContext(ConfigurationManager.AppSettings["ida:AADInstance"] + ConfigurationManager.AppSettings["ida:TenantId"], new TokenCache());
ClientCredential credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"], "client_secret");
var result = authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["ida:ClientId"], credential).Result.AccessToken;
This results in an error:
{"error":"invalid_grant","error_description":"AADSTS50105: Application >
'4337e286-7b84-4377-8843-82ea9504606b' is not assigned to a role for the application '4337e286-7b84-4377-8843-82ea9504606b'}
For other apps, it is set up with another app which has permissions for the resource, but I don't want to depend on another app to get the token.
Anyone an idea?
EDIT
I've followed the suggestion of #Jean-MarcPrieur, but on the line
var accounts = await application.GetAccountsAsync();
it returns no accounts which results in an empty result and no accessToken.
Here you are trying to use the client credentials flow (which requires your app to register permissions to call the Web API you want to call - tenant admin permissions).
If you want to access resources in the name of the user, you need to acquire a token for your app to call the API in the name of the signed-in user.
See for instance this sample: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/aspnetcore2-2-signInAndCallGraph which signs-in an user and calls the graph
It's an ASP.NET Core sample using the Azure AD v2.0 endpoint (the latest technos). If you have other constraints, don't hesitate to ask.
I have two applications
Client application build on ASP.NET MVC
Authentication server build on Web API + OWIN
Have planned authentication as follow
For user login client app will make a request to authication server with logged in credential.
Authication server will generate a token and will send back to client application.
Client application will store that token in local storage.
for each subsequent request client app will attached token kept in local storage in request header.
NOW, ON SERVER SIDE OF CLEINT APP I NEED TO VALIDATE THAT TOKEN COMES WITH EACH REQUEST IS NOT TEMPERED.
Please suggest me how to validate token in each request as i don't know the key the OWIN has used to generate the token.
Is is right to write code to validate token on client app or it should be on authication server.
I am planning to shift all user management code like register user, change password to authentication server so than we can re-use it for different client app- is it right design practice?
So far i have wrote below code to just to create a POC.
=========================OWIN configuration========
[assembly: OwinStartup(typeof(WebApi.App_Start.Startup))]
namespace WebApi.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new
OAuthBearerAuthenticationOptions());
}
}
}
==============================oAuth Provided========================
public class SimpleAuthorizationServerProvider: OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
Please help,
Thanks,
#Paul
Please suggest me how to validate token in each request as i don't
know the key the OWIN has used to generate the token.
Your current setup, were you have added the app.UseOAuthBearerAuthentication() to the owin pipeline, will authenticate the user from the bearer token which is passed on each request for you.
The current user can then be found via HttpContext.Current.User.
Use the Authorize attribute to then decide which users are authorized on certain endpoints.
Here's an example where users with the role "user" are allowed to access
[Authorize(Roles="user")]
public class ValuesController : ApiController
{
}
Is is right to write code to validate token on client app or it should
be on authication server.
NO, you don't validate the token in client, if your user credentials are wrong you wont get a token at all. That's all you need to know.
And also, why should you want to validate the token in the client?
I am planning to shift all user management code like register user,
change password to authentication server so than we can re-use it for
different client app- is it right design practice?
Reusing a token provider is common. Why invent the wheel for every application? Build one great, or use a third party, and reuse it across your applications.
Use JSON Web Tokens (JWT) and claims identities, not random tokens that require keeping track of the issued tokens.
A JWT is like a passport issued by a trusted authority. The passport is signed/stamped, and you can verify that it was issued by this trusted authority and that it has not been tampered with. That means, the integrity of the access-right claim present in the token can be verified without keeping state anywhere. The only communication that needs to happen between the trusting application and the authority is an initial (secure) download of the authority's public key (used for signing the tokens).
It's also advisable that you use a standard claims schema, like OpenID Connect ( http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims )
A good book on the topic, which helped me a lot getting an understanding of all these concepts, can be found here: A Guide to Claims-Based Identity and Access Control.
One way to verify a token has not been tampered is to sign it using an asymmetric key pair, Identity Server uses this approach as seen here.
In your case if you are rolling your own authentication you will need to implement this yourself, and check on every request probably in a custom middleware that the token is valid.
If you create, sendback, save in localStorage and every thing about JWT Token as correct, you have to know that many ways are in .Net that you can to controlling per request.
Server side controlling:
If you are using Web API Core, in core you can create Middleware that runs as pipline in run time, and you can give context and check token that requested, for more infomation check: This.
If you use of Asp.net MVC, you can use ActionFilter in MVC(Asp.Net-Core have more advance ActionFilter too), that each requests goes through on and you can check every thisng abount request, for more information check: This.
ClientSide Conftolling:
After that you give Token after log in from server side, you have to save data in localstorage that your browser check per request that data, they advantage are the Expireation and every like this issue in token save in localstorage and you and browser can use of this for more information check: This.
GoodLuck.
I am using this library https://github.com/openiddict/openiddict-core and implemented the oAuth token flow.
// Register the OpenIddict services.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
services.AddOpenIddict(options =>
{
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.AddMvcBinders();
// Enable the token endpoint (required to use the password flow).
options.EnableTokenEndpoint("/connect/token");
// Allow client applications to use the grant_type=password flow.
options.AllowPasswordFlow();
// During development, you can disable the HTTPS requirement.
options.DisableHttpsRequirement();
});
Mostly everything is working. I also included RoleManager along with it and roles are being stored in token as well using this:
var principal = await _signInManager.CreateUserPrincipalAsync(user);
foreach (var claim in principal.Claims.Where(c => c.Type == OpenIdConnectConstants.Claims.Role))
{
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
}
So far everything is done. After that admin user goes and modified the role to make him Supervisor etc. At this point of role change I want to make the previous tokens invalid. Is this possible in the library because at the moment he is able to use old token which has "Maid" role and is incorrect because somebody just changed his role to "Supervisor". In this case I want that when client sends old token it should not be treated as valid and should be sent back to login page.
How do I handle this in OpenIDDict library?
Assuming that I currently have a newly created project based on Visual Studio 2015 "WebApp" template with Individual Accounts authentication, I use Microsoft.AspNet.Authentication package and I can't always rely on cookies, because my web API should also target mobile apps:
How can I add authentication to my web API? I'm especially interested in token based authentication.
You can use basic http authentication or implement a similar one with a token or ticket passed through http headers.
Implement custom AuthorizeAttribute in your web api project. In IsAuthorized(HttpActionContext actionContext) overload you can check the authorization scheme and authorization header and then you can connect to your sessions provider and check if the user has an active session.
You must pass the login token in the authorization header, so if the token is missing that means there is no active user.
So when you login you must create and encrypt the token on successful login. Then pass this token with each request to the server.
This blog contains more information about using AuthorizeAttribute: http://weblogs.asp.net/jongalloway/asp-net-mvc-authentication-customizing-authentication-and-authorization-the-right-way
You can make separate table in db for storing authentication detail (AuthKey, UserID, CreatedDate, ExpiredDate, IsExpired) and make functions like CheckAuthorizationKey(string authKey), ExtendAuthorization(string authKey), ExpireAuthorization(string authKey){}
and call that functions for checking the authorization as below sample code.
public ServiceResult<LoginModel> Login(string auth_key)
{
var service = new ServiceResult<LoginModel>();
LoginModel user = new LoginModel();
if (AuthKey.CheckAuthorizationKey(auth_key) == false)
{
service.message = TemplateCodes.GetMessage(TemplateCodes.UnAuthorize, null, db);
service.status = ServiceStatus.authorization_failed;
return service;
}