I have a usual (tutorial-like) piece of code in Azure Service App. The HomeController is initialized as:
public HomeController(ILogger<HomeController> logger, GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
{
var task = Task.Run(async () => await m_tokenAcquisition.GetAuthenticationResultForUserAsync(new[] { "some.allowed.scope" }));
var context = task.Result;
var accessToken = context.AccessToken;
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Add("FeatureFlag", "00000004");
var newGraphClient = new GraphServiceClient(client);
}
The access token is good to go for 'GraphServiceClient'.
There is no use for the access token after it expires which happens in an hour or so. But the service needs to do periodic work on Azure account without bothering the user.
The main question is how should I proceed to prevent the user from frequent logins?
To access the refreshed token without asking users to login.
You need to customize the code for refreshing the token in application with the timer by checking the token expiry before it happens or by adding a listener for the token expiry event.
_timer = new Timer(TokenRefresh, null, _expiresIn * 1000 - 60000, Timeout.Infinite);
using (var client = new HttpClient())
{
var response = await client.PostAsync("https://authserver.com/token", new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grantType", "tokenRefresh"),
new KeyValuePair<string, string>("tokenRefresh", _tokenRefresh)
}));
var responseContent = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(responseContent);
_accessToken = (string)json["accessToken"];
_tokenRefresh = (string)json["tokenRefresh"];
_expiresIn = (int)json["expiresIn"];
//Expires in 1 hrs (3600)
}
_timer.Change(_expiresIn * 1000 - 60000, Timeout.Infinite);
You need to log into the application.
And the application has to send the login credentials to an authentication serverand has to verify them. And then returns an access token, refresh token.
Now you have the access token and refresh token securely on the client side.
When the access token expires, the application uses the refresh token to request a new access token from the authentication server.
The authentication server checks the refresh token and returns a new access token.
You need to customize the code for refreshing the token in application with the timer by checking the token expiry before it happens or by adding a listener for the token expiry event or delegate
As the refresh token has a shortlife, you need to use the new refresh token obtained from the token refresh process to get new access token again.
For Safety the refresh token and access token have to be stored securely and encrypted.
References taken from
Token Requests
Github Code
Related
I made the login method like this:
public async Task<IActionResult> Login([FromBody] LoginUserDTO userDTO)
{
var res = await _authManager.ValidateUser(userDTO);
if (!res) return Unauthorized();
await _authManager.SetLoginInfo(userDTO, Request);
return Accepted(new { Token = await _authManager.CreateToken() });
}
public async Task<string> CreateToken()
{
var signingCredentials = GetSigningCredentials();
var claims = await GetClaims();
var token = GenerateTokenOptions(signingCredentials, claims);
return new JwtSecurityTokenHandler().WriteToken(token);
}
How can I create an endpoint for Logout?
In ASP, there is no such thing as logging out from a JWT on the server.
A JWT is a token that has an expiry date and is issued by the server (or a trusted third-party). It is then cached by the client and sent to the server by the client in the header of subsequent requests and is then validated by the server to ensure that it is both valid and not expired.
If the expiry is reached, then the server will return a 401 - Unauthorised response.
If you want to log a client out then you just remove the client side cached token so that it cannot be sent in the header of any future requests.
I'm using firebase authentication dotnet library that can be find here
https://github.com/step-up-labs/firebase-authentication-dotnet
but I need to manage the refresh of the user token someone can help me?
actually my login function is this from where I can get the token but after 1 hour it expire
async private void authentication()
{
var authProvider = new FirebaseAuthProvider(new FirebaseConfig(apiKey));
var auth = await authProvider.SignInWithEmailAndPasswordAsync("email", "password");
userAuth = auth.FirebaseToken;
Console.WriteLine(auth.FirebaseToken);
}
thanks
We have one app that is using MSAL and generating a token with Azure AD. We need that app to call another API and we're passing the current token. That all is good. We have two different apps and I'll include some code to show how the audience (other client id) is able to authenticate
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.Audience = Configuration["AzureAd:ResourceId"];
opt.Authority = $"{Configuration["AzureAd:Instance"]}/{Configuration["AzureAd:TenantId"]}";
opt.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudiences = new string[] { "e8zzz-3304-43e0-aaaa-zzzzzzzzz" }
};
});
So authentication isn't a problem. We are using the GraphServiceClient to access the graph api, but the token that we get from the Authorization header is the token passed from the other app and has a client id tied to the other app. It all runs fine until it gets to the line with AcquireTokenAsync and the issue being returned states "Assertion audience does not match the Client app presenting the assertion. The audience in the assertion was '{other app id}' and the expected audience is '{app id of the api}' or one of the Application Uris of this application with App ID '{app id of the api}'(API APP). The downstream client must request a token for the expected audience (the application that made the OBO request) and this application should use that token as the assertion."
public async Task<GraphServiceClient> GetAuthenticatedClient()
{
var httpContext = this.httpContextAccessor.HttpContext;
if (httpContext != null)
{
StringValues authorizationToken;
httpContext.Request.Headers.TryGetValue("Authorization", out authorizationToken);
var authHeader = authorizationToken.FirstOrDefault();
if (authHeader != null && authHeader.StartsWith("bearer", StringComparison.OrdinalIgnoreCase))
{
var token = authHeader.Substring("Bearer ".Length).Trim();
var clientCredential = new ClientCredential(_authOptions.ClientId, _authOptions.ClientSecret);
var authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/{_authOptions.TenantId}");
var authenticationResult = await authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential, new UserAssertion(token));
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
return new GraphServiceClient(delegateAuthProvider);
}
}
throw new UnauthorizedAccessException("Invalid User token.");
}
The other point is that I have limited access to Azure AD beyond readonly. Another group controls that. It's kind of political so I am unable to update things to try things on my own and any changes take at least a day turnaround.
I also did set knownApplications in the manifest for the api.
I probably "could" have it use the client credentials from the other app just to see it work, but that's not a really good answer as we don't want to know those creds for other apps.. Surely, this is being done and I'm missing something obvious. Thanks so much ahead of time.. I hope, if anything, this is of use to someone else down the road.
Can you please try passing the token from App to API in the Postman and see if fetches the right App ID.
You can also try this sample for Calling a web API in a web app using Azure AD and OpenID Connect.
I was needing to retrieve a entity record from dynamics finance and operations and I did it in Postman but now I need to the same with code. I am using asp.net core and when I try to retrieve the entity the response gives me HTML. I figure out is because the authorization needs to be the same as the postman that is a post operation with grant_type, clientId, client secret and Resource.
How can I do post operation in C# with grant_type, clientId, client secret and Resource parameters to get the access token?
You have 2 options. In either of the cases, you might want to give a quick read to Microsoft identity platform and the OAuth 2.0 client credentials flow.
Using MSAL client library (note ADAL is deprecated now). Below is a quick code snippet. Full example can be found here. Also, read more here.
var app = ConfidentialClientApplicationBuilder.Create("<client id>")
.WithClientSecret("<client secret>")
.WithAuthority(new Uri("<authority>")) // authority = https://login.microsoftonline.com/{tenant}
.Build();
// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by a tenant administrator
var scopes = new string[] { "<scope>" };
var result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
var accessToken = result.AccessToken;
// call api with http authorization header
// Authorization: Bearer <Access Token>
Using REST API directly from C# code using httpClient.
var client = new HttpClient(); // just for example I am creating the client inline
var result = await client.PostAsync(
"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
new FormUrlEncodedContent(new[]
{
{"client_id", "<client id>"},
{"grant_type", "client_credentials"},
{"client_secret", "<client secret>"},
{"scope", "<scope>"},
}));
var responseBody = await result.Content.ReadAsStringAsync();
// response would be a JSON, just extract token from it
var accessToken = (string)JToken.Parse(responseBody)["access_token"];
// call api with http authorization header
// Authorization: Bearer <Access Token>
I am trying to use ADFS Authentication with OAuth to communicate between my webapp and webapi. I am using ADFS4 and have configured application group with Server application and Webapi accordingly. I am trying to receive the userdetails, particularly the username from the webapi controller. Is it possible to pass the username details within the access token passed to webapi. Here is what I did from the Webapp side:
In the webapp controller after adfs authentication,
authContext = new AuthenticationContext(Startup.authority, false);
ClientCredential credential = new ClientCredential(Startup.clientId, Startup.appKey);
string accessToken = null;
bool isAuthenticated = User.Identity.IsAuthenticated; //return true
string username = User.Identity.Name; // returns username
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value; // returns username
HttpClient httpClient = new HttpClient();
try
{
result = authContext.AcquireTokenAsync(Startup.apiResourceId, credential).Result;
accessToken = result.AccessToken;
}
catch (AdalException ex)
{
}
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = httpClient.GetAsync(Startup.apiResourceId + "/api/ConfApi").Result;
From the Webapi end, in Startup.Auth.cs, I have added these code
public void ConfigureAuth(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
MetadataEndpoint = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
TokenValidationParameters = new TokenValidationParameters() {
SaveSigninToken = true,
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
}
});
}
However, within the ConfApi controller, I cannot find any claims with user details.
What can I do to receive user details in the Webapi controller?
Thanks for any help.
Are you actually receiving the claims?
Did you configure claims rules for the web API on the ADFS side?
What did you use for Name - Given-Name, Display-Name etc?
Use something like Fiddler to monitor the traffic. After the OIDC authentication, you should see access tokens, id tokens etc.
Take the token and copy into jwt.io.
This will show you what you are actually receiving.
However, the OWIN classes translate the simple OAuth attributes e.g. "aud" into the claim type URI e.g. http://claims/this-claim so breakpoint and see what is in the claims collection and what type has been assigned to each.
The answer to this is the same answer to the question: MSIS9649: Received invalid OAuth request. The 'assertion' parameter value is not a valid access token
You have to use authorization code flow (instead of client credentials grant flow) to get the server app (web app in this case) to talk to the web API with the user's context. Authorization code flow will pass the claims in the JWT Token. Just make sure you pass thru any claims you need for the web API in the web API's RPT claim issuance transform rules.
Vittorio has a nice post on authorization code flow, although it talks about azure.
In order to use authorization code flow, you need to handle the AuthorizationCodeReceived Event via Notifications on the OpenIdConnectAuthenticationOptions from Startup.ConfigureAuth(IAppBuilder app)
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
...
Notifications = new OpenIdConnectAuthenticationNotifications {
AuthorizationCodeReceived = async code => {
ClientCredential credential = new ClientCredential(Startup.clientId, Startup.appKey);
AuthenticationContext authContext = new AuthenticationContext(Startup.authority, false);
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
code.Code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
Startup.apiResourceId);
}
}
When you are ready to make the call you acquire your token silently.
var authContext = new AuthenticationContext(Startup.authority, false);
var credential = new ClientCredential(Startup.clientId, Startup.appKey);
var claim = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userId = new UserIdentifier(claim, UserIdentifierType.UniqueId);
result = await authContext.AcquireTokenSilentAsync(
Startup.apiResourceId,
credential,
userId);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
result.AccessToken);