How do I use the Azure AD Authorization Code Flow? - asp.net

I am building an ASP.NET (v4.8) Web application that will be hosted as an Azure App Service, but for now we are on localhost.
I am configured for Azure AD successfully and I am receiving an authorization code because I configured my app service to send the access token. The app registration has ONLY User.Read (delegated) permissions.
In my Startup.cs file, I've configured OpenIdConnectAuthenticationNotifications so that I am receiving the access code in AuthorizationCodeReceived. Here is the code:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType );
app.UseCookieAuthentication(new CookieAuthenticationOptions());
authority = aadInstance + tenantId;
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
ClientId = clientId, Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
TokenResponseReceived = (tr) => { return Task.FromResult(0); },
AuthorizationCodeReceived = (code) => {
// you are here! what's next?
access_code = code.Code;
return Task.FromResult(0);
},
SecurityTokenReceived = (token) =>
{
return Task.FromResult(0);
},
AuthenticationFailed = (context) => { return System.Threading.Tasks.Task.FromResult(0); }
}
});
app.UseStageMarker(PipelineStage.Authenticate);
}
My objective is to call this graph endpoint as the current user to get their JobTitle and > Department from Azure AD. Here is the resource: https://graph.microsoft.com/v1.0/me
I was following this documentation, but it was not clear what to do with the provided access_code. Please help me understand.
Is this access_code a bearer token? can I use it directly to call the graph API?
Do I have to use it to call the /token endpoint to get a bearer token?
Do I have to use it to call the /authorize endpoint to get a bearer token?
I am making direct HTTP requests now, should I use MSAL or Graph SDK?
I think I am trying to accomplish this step:
This is the code I am currently working on, and it returns HTTP CODE 400 (Bad Request):
private void GetOtherProfileData()
{
var cId = Startup.clientId;
var tenantId = Startup.tenantId;
var scope = Startup.scope;
// scope: https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
var code = Startup.access_code;
var redir = HttpUtility.UrlEncode(Startup.redirectUri);
var req_url = $#"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token?client_id={cId}&scope={scope}
&code={code}&redirect_uri={redir}&grant_type=authorization_code
&code_verifier=ThisIsntRandomButItNeedsToBe43CharactersLong";
var req = WebRequest.CreateHttp(req_url);
req.Method = "POST";
req.ContentLength = 0;
req.ContentType = "application/x-www-form-urlencoded";
var resp = req.GetResponse();
var str = resp.GetResponseStream();
var json = new StreamReader(str).ReadToEnd();
Trace.TraceInformation(json);
/// this should return bearer token and then we go call the /me endpoint...
///right?
}
Any code samples or pointers to recent documentation would be helpful.

Is this access_code a bearer token? can I use it directly to call the graph API?
No, code and Access_token are different. You will need a access_token to call Graph API.
Do I have to use it to call the /token endpoint to get a bearer token?
Yes, you'll need code to call token endpoint to get the bearer token.
Do I have to use it to call the /authorize endpoint to get a bearer token?
You will get the code after calling authorize endpoint. You need to pass grant_type=code to get the code in response.
I am making direct HTTP requests now, should I use MSAL or Graph SDK?
You'll need to call Graph API after you get the access_token. Along with the token it also needs proper dedicated and application User permissions from Azure side.

I found a good sample here: https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect
I was trying to do this without a client secret, that was a mistake.
This is how I implemented it =>
AuthorizationCodeReceived = async (context) => {
// you are here!
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "User.Read" }, context.Code)
.WithSpaAuthorizationCode() //Request an authcode for the front end
.ExecuteAsync();
access_code = result.AccessToken;
// this is the bearer token.
},
This is what is inside the implementation of BuildConfidentialClientApplication:
clientapp = ConfidentialClientApplicationBuilder.Create(Startup.clientId)
.WithClientSecret(Startup.secret)
.WithRedirectUri(Startup.redirectUri)
.WithAuthority(new Uri(Startup.authority))
.Build();

Related

How do i implement google fit api in .net web app with user sign in?

I simply want to use google fit api to retrieve data of the signed in user(testing with two accounts).
But I don't understand how to get the authorization code/access token from the user.
Another possibly related problem, the consent page that list the scopes used doesn't appear.
I tried to use google.auth library
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets()
{
ClientId = " *** ",
ClientSecret = " *** "
}, new[] { FitnessService.Scope.FitnessActivityRead, FitnessService.Scope.FitnessActivityWrite, FitnessService.Scope.FitnessSleepRead, FitnessService.Scope.FitnessSleepWrite },
"user", CancellationToken.None);
FitnessService fitnessService = new FitnessService(new BaseClientService.Initializer()
{
ApplicationName = "Exrecise App",
HttpClientInitializer = credential
});
var resp = await fitnessService.Users.Sessions.List("me").ExecuteAsync();
But it takes me to this error, maybe I entered wrong inputs in console.cloud
Then, I copied the token from outhplayground and pasted it into httpclient
HttpClient http = new HttpClient();
var resp = await http.SendAsync(new HttpRequestMessage()
{
RequestUri = new UriBuilder("https://www.googleapis.com/fitness/v1/users/me/sessions").Uri,
Headers =
{
{ "Authorization","Bearer ya29.token" }
},
Method = HttpMethod.Get
});
This actually got me the results I wanted, but I want to get it from the user signing in.
Question: Can I not use the regular google authentication to achieve this? Or do I have to use the former method?
Here is me adding google authentication
builder.Services.AddAuthentication().AddGoogle(options =>
{
IConfigurationSection auth = builder.Configuration.GetSection("Authentication:Google");
options.ClientId = auth["ClientId"];
options.ClientSecret = auth["ClientSecret"];
options.CallbackPath = "/Home";
options.AuthorizationEndpoint += "?prompt=consent";
});
Is there something I'm missing?
Looks like the problem is that you did not supply a redirect uri (https://cloud.google.com/dotnet/docs/reference/Google.Apis/latest/Google.Apis.Auth.OAuth2.ICodeReceiver)
in GoogleWebAuthorizationBroker.AuthorizeAsync.
You need to add a redirect uri in the Google App and add it to the codeReceiver field. that's where the user will be redirected after accepting the app.
You can find more about machine authorization and OAuth2.0 authentication with google here

keycloack with dotnet simple API

Using this or https://nikiforovall.github.io/aspnetcore/dotnet/2022/08/24/dotnet-keycloak-auth.html tutorial I have setup test user and realm. I can call localhost:8080/realms/Test/protocol/openid-connect/token with client secret and user id and password from postman and it gives me access and refresh token. Now I need to call dotnet endpoint and make sure the user is who he is. But I can not find a way to establish this part as I'm always getting 401 unauthorized. Perhaps it is not setup or my authorization bearer string is not formed correctly.
How can I simply call to an endpoint, check authorization and return a response back?
Dotnet Code:
using System.Security.Claims;
using Api;
using Keycloak.AuthServices.Authentication;
using Keycloak.AuthServices.Authorization;
using Keycloak.AuthServices.Sdk.Admin;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
var host = builder.Host;
host.ConfigureLogger();
services
.AddEndpointsApiExplorer()
.AddSwagger();
var authenticationOptions = configuration
.GetSection(KeycloakAuthenticationOptions.Section)
.Get<KeycloakAuthenticationOptions>();
services.AddKeycloakAuthentication(authenticationOptions);
var authorizationOptions = configuration
.GetSection(KeycloakProtectionClientOptions.Section)
.Get<KeycloakProtectionClientOptions>();
services
.AddAuthorization(o => o.AddPolicy("IsAdmin", b =>
{
b.RequireResourceRoles("default-roles-test");
/*b.RequireRealmRoles("admin");
b.RequireResourceRoles("r-admin");
// TokenValidationParameters.RoleClaimType is overriden
// by KeycloakRolesClaimsTransformation
b.RequireRole("r-admin");*/
})
)
.AddKeycloakAuthorization(authorizationOptions);
var adminClientOptions = configuration
.GetSection(KeycloakAdminClientOptions.Section)
.Get<KeycloakAdminClientOptions>();
services.AddKeycloakAdminHttpClient(adminClientOptions);
var app = builder.Build();
app
.UseSwagger()
.UseSwaggerUI();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (ClaimsPrincipal user) =>
{
// TokenValidationParameters.NameClaimType is overriden based on keycloak specific claim
app.Logger.LogInformation("{#User}", user.Identity.Name);
return "Hello world. "+ user.Identity.Name;
}).RequireAuthorization("IsAdmin");
app.Run();
appsettings.json keycloack config:
"Keycloak": {
"realm": "Test",
"auth-server-url": "http://localhost:8080/",
"ssl-required": "none",
"resource": "test-client",
"verify-token-audience": false,
"client-secret": "P4JgvFhjY0ftGSLDYmYn7diZhjoLnHon",
"confidential-port": 0
}
Request sending to this endpoint from postman (perhaps the issue is here with correct sending format):

How to make Owin automatically use refresh token when access token expires

I have an Owin client connected to IdentityServer 4, and am wondering how to get owin to request a new access_token using the refresh token. I can successfully get owin to swap the code given for an access_token, id_token and refresh_token with the following configuration:
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookie"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "http://localhost:5000",
ClientId = "mywebsite",
ClientSecret = "secret",
RedirectUri = "https://localhost:5001/",
ResponseType = "code",
RequireHttpsMetadata = false,
SaveTokens = true,
UseTokenLifetime = true,
SignInAsAuthenticationType = "Cookie",
Scope = "openid profile email offline_access",
RedeemCode = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
Console.WriteLine(n);
return System.Threading.Tasks.Task.FromResult(0);
},
TokenResponseReceived = n =>
{
Console.WriteLine(n);
return System.Threading.Tasks.Task.FromResult(0);
}
},
});
}
Firstly, where do I save these tokens to? I can access them all the SecurityTokenValidated callback - should they go into the claims? Database? Memory?
Secondly, I have on my IdentityServer client configuration the access_token lifespan set to 60s, identity_token set to 3600s, and refresh to 30 days (please note the access_token is only this short for testing purposes). So how can I configure Owin to recognize that the access_token has expired and that it needs to go back to identityserver with the refresh_token and get a new one. Answers with example code snippets would be appreciated as my knowledge on all this is very small.
Relevant Info:
IS4 v3
.Net Framework v4.6
Client is set in IS to allow offline access
Take a look at this article:
Automatic Token Management for ASP.NET Core and Worker Services 1.0
Otherwise than that there is no logic in the AddOpenIdConnect(..) handler to deal with renewal of refresh tokens. I think its up to your application to refresh them. Refreshing them in code is not that hard if you have saved the refresh token somewhere safe.
See this question How to use 'refresh_token' in IdentityServer 4?

How to get new access token by using refresh token in AspNet.Security.OpenIdConnect.Server (ASOS)

I am using React as client and Web API core for back end interaction.
For Authentication we are using Token based authentication using AspNet.Security.OpenIdConnect.Server (ASOS).
I have to implement refresh token scenario where on expiration of access token we use refresh token (returned by ASOS) to get new access Token.
I know one way to achieve by calling method on client is in AXIOS interceptor like below.
httpPromise.interceptors.response.use(undefined, err => {
const { config, response: { status } } = err;
const originalRequest = config;
if (status === 401) {
var refresh_Token = JSON.parse(window.localStorage.getItem('refreshToken'));
fetch(globalConstant.WEB_API_BASE_PATH + "authtoken,
{
method: "POST",
headers: new Headers({
'Content-Type': 'application/json',
})
},
data:{grant-type:"refresh_Token",refresh_token:"refresh Token ....."
)
....other logic to set new access token and make call again to existing
request.
}
})
I want to done it in WEB API Core side, so that in middle ware or in authentication pipeline it detects access token expiration and return new access token. The glimpse of WEB API code is like below.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
.... some code
serives.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = OAuthValidationDefaults.AuthenticationScheme;
})
serives.AddOAuthValidation()
serives.AddOpenIdConnectServer(options =>
{
options.ProviderType = typeof(AuthorizationProvider);
options.Provider = new AuthorizationProvider(new SecurityService());
options.TokenEndpointPath = "/authtoken";
options.UserinfoEndpointPath = "/userInfo";
options.AllowInsecureHttp = true;
options.ApplicationCanDisplayErrors = true;
});
..some code
}
The links i followed How to handle expired access token in asp.net core using refresh token with OpenId Connect and https://github.com/mderriey/aspnet-core-token-renewal/blob/master/src/MvcClient/Startup.cs

Web Api to Signalr Azure token not working

I have a Asp.Net Web Api (Api1) that needs to send a message to a Signalr Realtime Api (Api2). I am trying to use Azure AD bearer tokens to authenticate. The client for Api1 is a JavaScript client that uses ADAL.js to get a token from Azure.
var authContext = new AuthenticationContext({
tenant: tenantId,
clientId: jsclientId,
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage',
endpoints: {
api1Url: api1ResourceUri
}
});
authContext.acquireToken(jsclientId, function (error, token) {
if (error || !token) {
authContext.clearCache();
authContext.login();
}
});
The JS client attaches this token in the Authorization header to all Api calls to Api1. In Api1 I am using the following code to get an access token from Azure AD.
var userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var result = await authenticationContext.AcquireTokenAsync(api2ResourceId, new ClientCredential(api1clientId, api1clientSecret), userAssertion);
I am attaching this access token to the request as an authorization header "Bearer tokenvalue". In the Signalr Hub Owin Startup class I have the following code.
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = api1Audiences,
SaveSigninToken = true
},
Tenant = configSection.TenantId
});
While the ClaimsIdentity on the hub is showing as authenticated, the user's identity is not being set. identity.name is null. It looks like the users identity is not being passed on to the Signalr hub.
You're getting no User Identity in your API because you are authenticating to it as an application, not as a user.
The acquireTokenAsync overload that only takes in resource and ClientCredentials is for the Client Credentials flow (a.k.a App-only flow).
What you need to do is to use the On-behalf-of flow to swap the token you got for API1 for a token for API2.
So in API1's Startup.Auth, TokenValidation parameters, set Save SigninToken to true like so:
app.UseOpenIdConnectAuthentication(
OpenIdConnectAuthenticationOptions
{
// ...
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true
},
// ...
});
And then wherever you want to call your API2, do as follows:
ClientCredential clientCred = new ClientCredential(clientId, appKey);
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
result = await authContext.AcquireTokenAsync(api2ResourceId, clientCred, userAssertion);
See it in the sample: https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof, specificaly the ToDoService's Startup.Auth.cs and TodoListController.cs.
NOTE: This sample is for native app + web api, you're adapting it for web app + web api.
Edit - Make sure your JS code is requesting a token for your API1 by specifying API1 in the JS acquireToken call like so:
var authContext = new AuthenticationContext({
tenant: tenantId,
clientId: jsclientId,
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage',
endpoints: {
api1Url: api1ResourceUri
}
});
authContext.acquireToken(api1clientId, function (error, token) {
if (error || !token) {
authContext.clearCache();
authContext.login();
}
});

Resources