Configure an asp.net core Web App to validate JWT token from ADFS - asp.net-core

I'm using ADFS 2019 and the scenario is:
Client App (trusted, client id and client secret)
Web Api (acts both as a server and as a client)
Resource to access
My GOAL is:
By using postman get a token from ADFS and call a Web API launched locally that must validate this token. Once the token has been validated it must generate another token (on-behalf-of) to access the last resource.
I can successfully get the first token specifying:
- Grant Type: Client Credentials
- Access Token URL: https://MY-ADFS/adfs/oauth2/token
- Client ID
- Client Secret
How can i configure my asp.net core Web Application to validate and accept this token?
I have all the data:
Web App identifier (for the server), web app client id/secret (when it acts as a client) and ADFS metadata endpoint.
I'm trying to do something like this:
services
.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://.../adfs";
options.Audience = "urn:microsoft:userinfo"; // taken from client token using jwt.io
options.MetadataAddress = "adfs metadata address";
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "https://.../adfs/services/trust",
ValidAudiences = new List<string> { "web app id" },
};
But it does not work (unauthorized or internal server error).
All these application are in the same application group in ADFS.
Thank you.
Update 1:
If i've understood correctly the audience must be WHO validates the token. So it must be the Web Api identifier inside ADFS.
If i put this identifier in the audience variable i get: audience did not match.
The audience that is in the token that i'm sending with postman is indeed different: urn:microsoft:userinfo!
Update 2:
I've managed to access to the web api and get a nice and valid access token. Now the problem is that the audience of the token is like:
"aud": "microsoft:identityserver:web api id on ADFS"
That "microsoft:identityserver is a problem when i have to do the "on-behalf of".
It forces me in doing:
ClientCredential clientCredential = new ClientCredential("microsoft:identityserver:client ID", "secret");
Otherwise it does not validate the audience.
But doing so, when i do:
var result = await authenticationContext.AcquireTokenAsync("resource to access' id", clientCredential, userAssertion);
It tells me that it cannot find a resource with client id "microsoft:identity:client id", and that's true, because the resource on ADFS has a client ID WITHOUT the "microsoft:identity" part.

Related

Create a personal access token (PAT) with OpenIddict

In OpenIddict, is it possible to have a second token endpoint that requires authorization and returns a long-lived token?
I'm converting a .Net Framework application to Core. As part of that I'm trying to swap the OAuth portions to OpenIddict. I've got standard authentication working using token endpoint just fine.
What I've been unable to do, or find an example of, is a second authenticated endpoint that generates a different token.
The purpose of the second endpoint is to provide a token similar to the PAT you get from GitHub or Azure DevOps
I was able to use this code to create a token on a second endpoint, but was not valid for authentication as I could not register it with OpenIddidct
var options = _oidcOptions.CurrentValue;
var descriptor = new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object>
{
{ "sub", "your user id" },
{ "scope", "your scopes" },
},
EncryptingCredentials = options.DisableAccessTokenEncryption
? null
: options.EncryptionCredentials.First(),
Expires = null, // recommended to set this
IssuedAt = DateTime.UtcNow,
Issuer = "https://contoso.com/", // the URL your auth server is hosted on, with trailing slash
SigningCredentials = options.SigningCredentials.First(),
TokenType = OpenIddictConstants.JsonWebTokenTypes.AccessToken,
};
var accessToken = options.JsonWebTokenHandler.CreateToken(descriptor);

Error using OpenID Connect with .NET Core 3.0 ASP.NET Core API service

I'm trying to implement OpenID Connect with Azure AD in a .NET Core 3.0 ASP.NET Core API service.
It is an API service only, with no UI, and a separate SPA is used to access the API service.
Per the instructions here, I'm sending the sign-in request from my SPA, which, after authentication is being redirected back to the /signin-oidc endpoint on my API.
At this point, I get an error:-
Error from RemoteAuthentication: "Unable to unprotect the message.State.".
The initial request from the SPA looks like this:-
tenantId = my Azure AD tenant ID
clientId = my Azure AD application ID
responseType = "id_token"
redirectUri = "http://localhost:12345/signin-oidc"
scope = "openid"
responseMode = "form_post"
state = "12345"
nonce = "67890"
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize?client_id={clientId}&response_type={responseType}&redirect_uri={redirectUri}&scope={scope}&response_mode={responseMode}&state={state}&nonce={nonce}
And my API startup code looks like this:-
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureAD(o =>
{
o.ClientId = "(tenant id)";
o.TenantId = "(client id)";
o.Instance = "https://login.microsoftonline.com/";
})
.AddAzureADBearer(o =>
{
o.ClientId = "(tenant id)";
o.TenantId = "(client id)";
o.Instance = "https://login.microsoftonline.com/";
});
If I omit the state parameter from the initial request, I get a different error:-
Error from RemoteAuthentication: "OpenIdConnectAuthenticationHandler: message.State is null or empty.".
The referenced instructions say that state is recommended, not required:-
A value included in the request that also will be returned in the token response. It can be a string of any content you want. A randomly generated unique value typically is used to prevent cross-site request forgery attacks. The state also is used to encode information about the user's state in the app before the authentication request occurred, such as the page or view the user was on.
However, the errors I'm getting seem to imply that state is required, and needs to be specially generated.
I've also tried with the following startup code, and get the same errors:-
services.AddAuthentication(options =>
{
options.DefaultScheme = Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ClientId = "(client id)";
options.Authority = "https://login.microsoftonline.com/" + "(tenant id)";
});
The problem I think is that you can not send requests directly to /signin-oidc from your SPA application. Because the AddOpenIdConnect(...) handler requires that it is the one who makes the initial request to AzureAd and it that it also who receives the request back to /signin-oidc.
AddOpenIdConnect sets its own state value and passes is to the Identity provider aand it expects to see the same state value later when the browser calls /signin-oidc.

Identity Server 4 Client Credentials for custom endpoint on token Server

I implemented a token server using Identity Server 4.
I added a custom API endpoint to the token server and struggle with the authentication. The custom endpoint is inherited from ControllerBase and has 3 methods (GET, POST, DELETE).
I intend to call the custom endpoint from within another API using a dedicated client with credentials (server to server) implemented as HttpClient in .NET Core. There is no user involved into this.
For getting the access token I use the IdentityModel DiscoveryClient and TokenEndpoint.
So in sum I did the following so far:
setup "regular" identity server and validate it works -> it works
implement custom endpoint and test it without authorizatio -> it works
add another api resource ("api.auth") with a custom scope "api.auth.endpoint1"
setup a client with client credentials allowing access to scope "api.auth.endpoint1".
implement the HttpClient and test setup -> I get an access token via the Identity Model Token Endpoint.
Now, when I call the endpoint using the HttpClient with the access token I received I get response code 200 (OK) but the content is the login page of the identity server.
The documentation of Identity Server 4 state the use of
services.AddAuthentication()
.AddIdentityServerAuthentication("token", isAuth =>
{
isAuth.Authority = "base_address_of_identityserver";
isAuth.ApiName = "name_of_api";
});
as well as the use of
[Authorize(AuthenticationSchemes = "token")]
Unfortunatly the compiler state that .AddIdentityServerAuthentication can't be found. Do I miss a special nuget?
The nugets I use on the token server so far are:
IdentityServer4 (v2.2.0)
IdentityServer4.AspNetIdentity (v2.1.0)
IdentityServer4.EntityFramework (v2.1.1)
Figured out that part. The missing nuget for AddIdentityServerAuthentication is:
IdentityServer4.AccessTokenValidation
Struggling with the authorization based on the custom scope.
Does anyone know how the security has to be configured?
Configure a client with ClientGrantTypes = client_credentials and your api like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth";
});
Where ApiName is the name of the resource. Please note that resource != scope. In most samples the resource name is equal to the scope name. But not in your case, where resource name is api.auth and scope name is api.auth.endpoint1.
Configure the client to request the scope.
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api.auth.endpoint1");
IdentityServer will lookup the Resource name and add that to the token as audience (aud) while the scope is added as claim with type scope.
This should be enough to make it work. Also check the sample project.
Custom authentication scheme and scope based policies for different access rights bundled together looks like that:
// Startup.ConfigureServices
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("CustomAuthEndpointsAuthenticationScheme", options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth"; //IdentityServer4.Models.ApiResource.Name aka Audience
});
services.AddAuthorization(options =>
{
options.AddPolicy("Endpoint1Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint1"); } ); //IdentityServer4.Models.Scope.Name
options.AddPolicy("Endpoint2Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint2"); } ); //IdentityServer4.Models.Scope.Name
} );
// securing the custom endpoint controllers with different access rights
[Authorize(AuthenticationSchemes = "CustomAuthEndpointsAuthenticationScheme", Policy = "Endpoint1Policy")]
It seems not to interfere with the IdentityServer4 default endpoints nor with the ASP.NET Core Identity part.

OAuth 2.0 , Azure AD and OpenId Connect

I am still trying to understand OAuth 2.0 flows with ASP.Net Core 2.0. The default code (see below) which was provided by Microsoft works well with Azure AD authentication using OAuth 2.0 and OpenId Connect.
I am actually testing the Authorization Code flow.
I am trying to change the code below so that it doesnt use OpenId Connect but instead use the plain OAuth. (You may want to ask why, the vendor I am working with is not supporting the OpenId Connect yet).
So I need to use plain OAuth to enable to Authorization Code flow using Azure AD.
services.AddAuthentication(auth =>
{
auth.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("Authentication").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
HttpRequest request = ctx.HttpContext.Request;
//We need to also specify the redirect URL used
string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
//Credentials for app itself
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = ctx.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(ctx.Principal);
var authContext = new AuthenticationContext(ctx.Options.Authority, cache);
//Get token for Microsoft Graph API using the authorization code
string resource = "https://bupaau.onmicrosoft.com/4fa4b4a7-d34f-49af-8781-c8b39f0cf770";
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
//Tell the OIDC middleware we got the tokens, it doesn't need to do anything
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
});
How do I turn the openId Connect off and enable the plain OAuth authentication for Authorization Code flow.
-Alan-
You specify this by using scope parameter values. For OpenID Connect, scope value is set to openid. This is what specification says about authorisation request.
But there are some Azure AD specifics you need to taken care of. This is highlighted in Azure AD documentation. For OpenID Connect,
When your web application needs to authenticate the user, it must direct the user to the /authorize endpoint. This request is similar to the first leg of the OAuth 2.0 Authorization Code Flow, with a few important distinctions:
The request must include the scope openid in the scope parameter.
The response_type parameter must include id_token.
The request must include the nonce parameter.
Also here is the link for OAuth 2.0 documentation. You may not simply remove OpenID Connect specific parameters to get OAuth 2.0 response as there could be implementation specific requirements.

Authentication and Authorization with ASP.NET Core and Service Stack

I have a ASP.Net Core MVC Web App that users needs to logon to get the id_token from the IdentityServer4 and then that id_token will be passed to webapi implemented in ServiceStack to obtain the authorization code. The subsequent call to the webapi will use the authorization code.
So far what I have read is for the Web App, it should use openid cookie token (UseOpenIdConnectAuthentication). For the webapi, it should use the bearer token. My question is how I can pass that http only cookie token from the client side browser as a bearer token in the http header. As the cookie is http only, it can't be accessed by the Javascript. Moreover, the ASP.NET Core cookie middleware encrypts the cookie, can that encrypted cookie be decrypted by the ServiceStack webapi (if the cookie is passed to the webapi)?
Am I going in the right direction? Any suggestion is welcome.
Thanks
You can find an example of your scenario here: https://identityserver4.readthedocs.io/en/release/quickstarts/5_hybrid_and_api_access.html
The authorization code is only used to get access tokens from the identity server, it is not used to authenticate to APIs.
Here is how the flow should work:
User logs in at Identity Server
Your MVC app gets an authorization code and id token
The id token tells your MVC app who the user is
The authorization code is exchanged for an access token and refresh token with identity server for the API
Now the MVC app can make HTTP calls from its backend using the access token
Authentication cookie is created and returned to user
Front-end submits the authentication cookie with every request to MVC backend, which authenticates every request automatically that hits MVC, then when you want to call the API from there, get it as shown in the docs, and attach it to your requests
I think the point you are missing here is that once the user is logged in, you will get the access token in the response as well when you land back on the client application. If you are using Hybrid Flow, on the client app we configure it as
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ClientId = "mvc",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "api1", "offline_access" },
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
});
See the ResponseType we ask for code i.e the access code. So you need not to call or login again. Once you want to call your api just get the token like
var access_token = await HttpContext.Authentication.GetTokenAsync("access_token");
// call api
var client = new HttpClient();
client.SetBearerToken(access_token);
var response = await client.GetAsync("http://localhost:5001/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
And if you using Implicit flow, your front end can get the access token using oidc-client library and user.access_token will have it.