Redirect URI with Client Credential Flow - asp.net-core

I am looking into using MSAL and client credential flow, however, there is one thing I don't fully understand.
In the example provided by Microsoft:
https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/blob/master/daemon-console/Program.cs
The following code is used to get an access token:
var clientCredentials = new ClientCredential(_clientSecret);
var app = new ConfidentialClientApplication(_clientId, _authority, "https://daemon", clientCredentials, null, new TokenCache());
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = await app.AcquireTokenForClientAsync(scopes);
Whats with the redirectUri in this case?
I have tried different values as the redirectUri and it seems to work either way... but if I add a relative path or null it fails to obtain a token. What is this value supposed to be?
For a console application it makes little sense to listen on an URL, however, the documentation for ConfidentialClientApplication says that it is required.

To request access token with client credential flow , app will send HTTP POST token request to Azure AD's token endpoint with app's credential , AAD will return access token in response , redirect url is not need in this scenario . According to source code , the redirect url is not used also:
private async Task<AuthenticationResult> AcquireTokenForClientCommonAsync(IEnumerable<string> scopes, bool forceRefresh, ApiEvent.ApiIds apiId, bool sendCertificate)
{
Authority authority = Instance.Authority.CreateAuthority(ServiceBundle, Authority, ValidateAuthority);
AuthenticationRequestParameters parameters = CreateRequestParameters(authority, scopes, null,
AppTokenCache);
parameters.IsClientCredentialRequest = true;
parameters.SendCertificate = sendCertificate;
var handler = new ClientCredentialRequest(
ServiceBundle,
parameters,
apiId,
forceRefresh);
return await handler.RunAsync(CancellationToken.None).ConfigureAwait(false);
}
But you should provide a valid url when initializing the ConfidentialClientApplication at this point .

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

Microsoft Graph access token refresh

I am writing an application that uses the "OAuth 2.0 client credentials grant flow" to get an access token for calling the Microsoft Graph API. The application authenticates as itself, not on behalf of a signed in user.
I based my code off of this example from Microsoft.
This is how I initialize the GraphServiceClient:
// Read application settings from appsettings.json (tenant ID, app ID, client secret, etc.)
AppSettings config = AppSettingsFile.ReadFromJsonFile();
// Initialize the client credential auth provider
var scopes = new[] { "https://graph.microsoft.com/.default" };
var clientSecretCredential = new ClientSecretCredential(config.TenantId, config.AppId, config.ClientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
And this is how I later use it (for example):
var users = await graphClient.Users.Request().GetAsync();
My application is an API. It is not an application that runs once and done. It will be continuously running for a long time. So I am concerned about what will happen when the access token expires. How do I make sure that when I need to use the graphClient the access token will not be expired?
According to your code snippet above, I think you are using the graph SDK and using the client credential flow as the authentication.
So we are no need to generate access token here but just using the graphClient to call the graph api and gather the information you needed. And due to this mode, it won't appear the token expired situation as each time you call an api you will new clientSecretCredential before it.
And let's come back to the refresh, azure ad provide refresh token for refreshing the access token when it expired as refresh token has much longer expire time than access token, when we try to get the refresh token, we need to append offline_access to the scope when generate the access. But using client credential flow means your app requests a new token with it's own credentials, so it's no need to using refresh token to avoid making signed-in user sign in again. Using credential flow shouldn't return refresh token.
Then you may have some ideas that you insist on using refresh the expired token process, then what you only can do is generate an access token first and save the token with its expired time in some place, and using the access token as the http request header and calling graph api. Then the code should like this, but I don't think you're willing to using this kind of code, you may also refer to this document for more details:
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "your_azuread_clientid";
var clientSecret = "corresponding_client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var tokenRequestContext = new TokenRequestContext(scopes);
var token = clientSecretCredential.GetTokenAsync(tokenRequestContext).Result.Token;
//using http sender with the token
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token );
// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

No Session handle to Renew Token in Xero-Partner-App

Error from Xero :
Token does not match an expected REQUEST token
The issue seems to be related to the comment from the Xero help site?
This error will also occur if the session handle is not used in the
access token renewal process.
I have confirmed I do not get this on the return object of the following :
public async Task<AccessTokenDto> Authorise(string oauth_token, string oauth_verifier, string org)
{
var xeroacessToken =
_authenticator.RetrieveAndStoreAccessToken(_user.Name,
oauth_token, oauth_verifier, org);
}
I am not sure how I get the session handle and then how to use this in my service call to renew the token? As per the example I was expecting this on my token.
oauth_session_handle=ODJHMGEZNGVKMGM1NDA1NZG3ZWIWNJ
"Session Handle used to renew the access token"
Code that works the first time with the token that has been retrieved from the database:
var tokenStore = new MemoryTokenStore();
tokenStore.Add(xerotoken);
var api = new RA.Xero.Partner.Core(tokenStore, XeroUser(UserId)), _hostingEnvironment)
{
UserAgent = "My Partner App " + input.ConsumerKey,
};
I tried to see if using the Partner Authentication directly would work :
var tokenStore = new MemoryTokenStore();
tokenStore.Add(xerotoken);
Settings ApplicationSettings = new Settings();
X509Certificate2 certificate = RA.Xero.Partner.Core.Certificate(_hostingEnvironment);
var partnerAuthentication = new RA.Xero.Public.PartnerAuthenticator(
ApplicationSettings.Uri,
ApplicationSettings.AuthorizeUri,
ApplicationSettings.CallBackUri,
tokenStore,
certificate
);
var consumer = new Consumer(ApplicationSettings.Key,
ApplicationSettings.Secret);
var token = partnerAuthentication.GetToken(consumer,
XeroUser(UserId));
I have checked the keys are the partner keys in my app and any hints or sample code would be great.

How to fetch email in web api 2 which is secured by WAAD

I am using MVC 5 client which is secured by "UseOpenIdConnectAuthentication" and getting all user details in Claims object, this client is calling WAAD secured Web Api by "Bearer" authentication token.
I need to fetch username or email in the web api. I tried different options but nothing worked.
I am getting null in Identity.Name, other properties I am getting like nameidentifier, objectidentifier, tenanted etc.
Please advise.
Thanks
Below code I am using for access token in Web Client.
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
ClientCredential clientcred = new ClientCredential(Startup.clientId, Startup.appKey);
AuthenticationContext authenticationContext = new AuthenticationContext(Startup.aadInstance + Startup.tenantId, new ADALTokenCache(signedInUserID));
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(apiResourceId, clientcred);
return authenticationResult.AccessToken;
Start up Code
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
}
Below are the Token Details:
You can get the upn of current user by :
var upn = ClaimsPrincipal.Current.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn").Value;
Other way is getting user's basic information using Microsoft Graph api , please refer to On-Behalf-Of scenario .The OAuth 2.0 On-Behalf-Of flow serves the use case where an application invokes a service/web API, which in turn needs to call another service/web API. Please refer to protocol explanation and code sample .
Update :
Looking into your codes , you are using client credential flow to acquire token for your web api :
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(apiResourceId, clientcred);
The OAuth 2.0 Client Credentials Grant Flow permits a web service (confidential client) to use its own credentials instead of impersonating a user, to authenticate when calling another web service. That's why you can't get upn information which associated with a user .
You can use authorization code flow with user's identity , Please refer to code sample :
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
result = await authContext.AcquireTokenSilentAsync(todoListResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));

JWT authentication concept

I am currently working on an interaction between Angular JS app and Node.js Server (as API) with an authentication based on JSON Web Token.
But I have a question I can't answer by myself : when you encode the JWT server-side putting a user as payload, how do you proceed to retrieve the user information client-side ?
Here is a small example to understand my question:
I am a basic user, I send my credentials to the API for authenticating. In exchange, I receive a JWT token but I don't have any information about the user since only the server has the secret key that is able to decode the JWT token. So does the server need to send me for example the id of the user so that I could call my api user/id for retrieving information about the user authenticated?
You retrieve the user's info by decoding the token on each request. So in your example after the token is returned to the client, the client makes a request to the server to grab the user's first and last name using the data stored in the encoded token which is sent along with the request back to the server. When making this GET request, you can send the token as a parameter. I'll use a non-cookie stored example. Here's how it goes down:
The user signs in with their password and username
The server encodes a json web token payload that contains the unique identifier (i.e. user_id) of the user that signed in using the secret_key. An example function call may look something like this.
payload = {user_id: 35}
user_token = JWT.encode(payload, "your_secret_key");
Return the user_token to the client and store said token in a hidden html tag or in a localStorage variable. Using Angular, I'd store it in localStorage.
Now that the user is signed_in and the token is client-side, you can submit a GET request that contains the user_token as a parameter. Remember, this user_token payload contains the user_id.
The server gets the parameter and decodes the user_token to get the user_id from the payload.
You query the database with the user_id and return the data (first and last name) as plain json, NOT ENCODED.
It's important to remember the only thing to encode in your example is the unique identifier (user_id). On each request you decode the token which itself is the authentication mechanism.
You have the payload on the client, If your needed data is in the payload you can easily do a Base64 Decode on payload to find it!
To understand this here are steps:
Client send username:user,password:pass to server.
The server starts the authentication business and finds that the user name and password is valid.
The server must return these information back to client. Here is where JWT has some rules. The server must return a token back to client. The token has three parts Header.PayLoad.Signature . Forget about signature right now, which is the part which make some confusion.
The part one is Header. Some thing like:
{"typ":"JWT","alg":"HS256"}
Which will be eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 after Base64 Decode. Please consider this is just a decode, no encryption at all! To see this you can go to https://www.base64decode.org/ and test.
After the header, the server needs to send a payload to user. The server may decide to send below json ( I said decide, because there is no standard requirement here, you can send more or less data as payload, for example, you may also set user privileges for example admin:true, or user first and last name, but keep in mind that the JWT size must be small as it will be send to server on each request)
{"username":"user","id":3,"iat":1465032622,"exp":1465050622}
Again according to JWT, the server needs a Base64 Decode here ( and again no encryption at all). The above json will be eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.
Until now the server created the Header and Payload. Now time to make signature! It is very easy:
var encodedString=base64UrlEncode(header) + "." + base64UrlEncode(payload);
//As our example base64UrlEncode(header) is eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
//and the base64UrlEncode(payload) is eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9
var signature=HMACSHA256(encodedString, 'a secret string which is kept at server');
The signature is made with a secret key which you don't have it at clent!! You don't need it either. All token data is in the payload and can be accessed with decode ( again no decrypt ! ).
This signature is used at the server, when you send token back to server, the server check that signiature is correct to make sure he can trust the token data.
To summarize have a look at below token
//Header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
//PayLoad
eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.
//Signature
0K8TL1YS0XKnEIfI3lYs-bu2vbWHSNZsVJkN1mXtgWg
Header and payloads are Base64 Decoded and you can encode it on client. But you can not do any thing with signature.
The signature is only used by the server. The client send each request with his token, the server must be sure that the client did not change any part of token payload (for example change userid). This is where the signature string come importance is revealed, the server recheck the signature with it's secret key for every request!
Note:
Do you still wonder why the JWT use encode and decode ?! To make the hole token URL safe !
The strategy in the accepted answer works, but it misses the fact that the client can see the payload of a JWT. It is explained nicely in The Anatomy of a JSON Web Token.
A JWT has 3 parts. The first two, header and payload, are base64 encoded. The client can decode them easily. The payload has claims about the user, the client can use this data (user id, name, roles, token expiration) w/out having to make another request to the server.
The third part of the JWT is the signature. It is a hash of the header, the payload, and a secret that only the server knows. The server will validate the token and user's permissions on every request.
The client never knows the secret, it just has a token that claims to be the given user.
JWT (JSON web token) has become more and more popular in web development. It is
an open standard which allows transmitting data between parties as a JSON object in a secure and compact way. The data transmitting using JWT between parties are digitally signed so that it can be easily verified and trusted.
JWT in ASP.NET Core
The first step is to configure JWT based authentication in our project. we can add custom jwt auth middleware that fire in every request for Authorization.
Startup.cs
services.AddMvc(options => options.EnableEndpointRouting = false);
var tokenValidationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("Jwt_Key"),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
RequireExpirationTime = false,
ValidIssuer = "Jwt_Issuer",
ValidAudience = "Jwt_Audience",
ClockSkew = TimeSpan.Zero
};
services.AddSingleton(tokenValidationParams);
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwt => {
jwt.SaveToken = true;
jwt.TokenValidationParameters = tokenValidationParams;
});
services.AddMvc();
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// custom jwt auth middleware
**app.UseMiddleware<JwtMiddleware>();**
app.UseAuthentication();
app.UseMvc();
app.Run(async (context) =>
{
await context.Response.WriteAsync("Welcome to DATA API");
});
}
Generete JWT
GenerateJSONWebToken(User userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Jwt_Key"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserID),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken("Jwt_Issuer","Jwt:Audience",
claims,
expires: DateTime.Now.AddHours(24),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
This Method return JWT Totken like
Token : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKa
WduZXNoIFRyaXZlZGkiLCJlbWFpbCI6InRlc3QuYnRlc3RAZ21haWwuY29tIiwiRG
F0ZU9mSm9pbmciOiIwMDAxLTAxLTAxIiwianRpIjoiYzJkNTZjNzQtZTc3Yy00ZmU
xLTgyYzAtMzlhYjhmNzFmYzUzIiwiZXhwIjoxNTMyMzU2NjY5LCJpc3MiOiJUZXN0
LmNvbSIsImF1ZCI6IlRlc3QuY29tIn0.8hwQ3H9V8mdNYrFZSjbCpWSyR1CNyDYHc
Gf6GqqCGnY"
Calling Authorize Method
[Authorize]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2", "value3", "value4",
"value5" };
}
Validate Token in Jwt Middleware Class
JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly TokenValidationParameters _tokenValidationParams;
public JwtMiddleware(RequestDelegate next, TokenValidationParameters
tokenValidationParams)
{
_next = next;
_tokenValidationParams = tokenValidationParams;
}
public async Task Invoke(HttpContext context)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var jwtTokenHandler = new JwtSecurityTokenHandler();
// Validation 1 - Validation JWT token format
var tokenInVerification = jwtTokenHandler.ValidateToken(token, _tokenValidationParams, out var validatedToken);
if (validatedToken is JwtSecurityToken jwtSecurityToken)
{
var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase);
if (result == false)
{
Error Invalid = new Error()
{
Success = false,
Errors = "Token is Invalid"
};
context.Items["Error"] = Invalid;
}
}
await _next(context);
}
}
Authorize Attribute
public void OnAuthorization(AuthorizationFilterContext context)
{
var Error= (UserModel)context.HttpContext.Items["Error"];
if (AuthResult != null)
{
// not logged in
context.Result = new JsonResult(new { message = "Unauthorized Access" }) {
StatusCode = StatusCodes.Status401Unauthorized };
}
}
I hope this will work for you.