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

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

Related

Microsoft.Owin.Security.OpenIdConnect AuthenticationTicket is null on AuthorizationCodeReceived

I'm using OpenIdConnectAuthentication with code flow to implement the OpenIdConnect login. But on the AuthorizationCodeReceived, the property notification.AuthenticationTicket is null value. Any advice?
Here is my startup:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
string auth0RedirectUri = "http://localhost:44335/";
app.UseCookieAuthentication(new CookieAuthenticationOptions(){});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "OIDC",
ClientId = "qKu-JoUguDjzrvBm*****",
ClientSecret = "w7JPnYYIttT8aDYPrZL9lvQzNaXP0QDqyVMu4AHZYWkUrczG4WJThmo3blHEvfz*******",
Authority = "https://******/authorize",
RedirectUri= auth0RedirectUri,
ResponseType = OpenIdConnectResponseType.Code,
Scope = OpenIdConnectScope.Email+" "+OpenIdConnectScope.OpenIdProfile,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false // This is a simplification
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (notification) =>
{
Debug.WriteLine("*** AuthorizationCodeReceived");
//TODO: get access token from token endpoint later
var authClaim = new ClaimsIdentity("OIDC", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
authClaim.AddClaim(new System.Security.Claims.Claim("Email","abc#mail.com"));
// notification.AuthenticationTicket is null
notification.AuthenticationTicket = new AuthenticationTicket(authClaim, notification.AuthenticationTicket.Properties);
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
Debug.WriteLine("*** AuthenticationFailed");
return Task.FromResult(0);
},
},
UsePkce = false
});
}
I was recently diving into OpenIDConnect on old ASP.NET framework and had a lot of truble as well.
It will be very difficult to answer you question - becasue I don't know what exactly you want to achieve. Basically as far as I understand the flow AuthorizationCodeReceived gets triggered when user logs-in on authentication server side and gets navigated back with Code query parameter. So at this point AuthenticationTicket should be null because nothing really set it yet.
Now developer has a choice if you want to handle CodeRedemption themselves or leave that to the OpenIDConnectAuthenticationHandler.
I personally did not use first option. I used this step only to transform code into jwe token because my Auth server requires it. But if your choice is to handle it youself then probably you need to do something like in samples available on Katana github project:
AuthorizationCodeReceived = async n =>
{
var _configuration = await n.Options.ConfigurationManager.GetConfigurationAsync(n.OwinContext.Request.CallCancelled);
var requestMessage = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, _configuration.TokenEndpoint);
requestMessage.Content = new System.Net.Http.FormUrlEncodedContent(n.TokenEndpointRequest.Parameters);
var responseMessage = await n.Options.Backchannel.SendAsync(requestMessage);
responseMessage.EnsureSuccessStatusCode();
var responseContent = await responseMessage.Content.ReadAsStringAsync();
Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage message = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage(responseContent);
n.HandleCodeRedemption(message);
}
https://github.com/aspnet/AspNetKatana/blob/beb224c88712b08ce45f1d14bb8cf0cd9d4a8503/samples/Katana.Sandbox.WebServer/Startup.cs#L157
If you will choose not to do it yourself then you will have to set RedeemCode = true on OpenIdConnectAuthenticationOptions. Then handler will get the token and will set context properly.
There is not much of a documentation anywhere but for me very usefull was Katana project on github. Almost whole flow is implemented in https://github.com/aspnet/AspNetKatana/blob/main/src/Microsoft.Owin.Security.OpenIdConnect/OpenidConnectAuthenticationHandler.cs
You can check what is the flow and what each Notfication is used for.
Unfortunately I cannot help you much more because each flow might be different and only way to say in detail what needs to be done is to reproduce your specific environment.

How do I use the Azure AD Authorization Code Flow?

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

Multiple Authentication Middlewares ASP.NET Core

I am relatively new to the concept of middlewares. I am aware that a middleware calls the next middleware when it completes.
I am trying to authenticate a request using either Google or my Identity Server. The user can login on my mobile app with google or a local account. However, I can't figure out how to use both authentication middlewares. If I pass the id_token for google, it passes on the first middleware (UseJwtBearerAuthentication) but fails on the second one (UseIdentityServerAuthentication). How can I make it so that it doesn't throw error when it actually passes on at least 1 authentication middleware? For example, if it passes on the first middleware, the second middleware is ignored?
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
Authority = "https://accounts.google.com",
Audience = "secret.apps.googleusercontent.com",
TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidIssuer = "accounts.google.com"
},
RequireHttpsMetadata = false
});
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:1000/",
RequireHttpsMetadata = false,
ScopeName = "MyApp.Api"
});
Normally, when an authentication middleware is failed(i don't mean throwing exception), this doesn't affect another successful authentication middleware. Probably your second middleware throws an exception(not a validation failure). First check error message and try to resolve it. If you can't, use AuthenticationFailed event to handle error. In this case your code should be something like below:
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
// ...
Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async (context) =>
{
if (context.Exception is your exception)
{
context.SkipToNextMiddleware();
}
}
}
});
However, for your scenerio i wouldn't choose your way. I would use only identity server endpoint. For signing with google you can configure identity server like below:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
AutomaticAuthenticate = false,
AutomaticChallenge = false
});
app.UseGoogleAuthentication(new GoogleOptions
{
AuthenticationScheme = "Google",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = "",
ClientSecret = ""
});
app.UseIdentityServer();
Edit
It seems AuthenticationFailed event couldn't be used for IdentityServer4.AccessTokenValidation. I am not sure but if you will use identity server for only jwt token, you can use UseJwtBearerAuthentication for validation.

OpenIDConnect AspNetCore Logout using id_token

Main issue is that I could not find a proper way to logout from identityServer4.
Detailed explanation:
Client side Web application startup.cs contains the following code
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:1941/",//local identityServer4
ClientId = "testsoft",
ClientSecret = "secret",
ResponseType = "code id_token token",
GetClaimsFromUserInfoEndpoint = true,
RequireHttpsMetadata = false,
Scope = { "openid", "profile", "email" },
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "name",
RoleClaimType = "role"
},
AutomaticAuthenticate = false,
AutomaticChallenge = true
});
IdentityServer4 running locally has the client added as below
new Client
{
ClientId = "testsoft",
ClientName = "testsoft",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = "http://localhost:55383/",//clientside web application url
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true,
RedirectUris = new List<string>
{
"http://localhost:55383/signin-oidc"
},
RequireConsent = false,
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.Email.Name,
StandardScopes.Roles.Name,
StandardScopes.OfflineAccess.Name,
"api1", "api2",
},
},
I was able to login and display the claims on a Controller-View in MVC like this
[Authorize]
public IActionResult About()
{
return View((User as ClaimsPrincipal).Claims);
}
And the view displayed was like this. Note that there is no id_token
And I was able to logout using cookie as given below
public async Task<IActionResult> LogOut()
{
await HttpContext.Authentication.SignOutAsync("Cookies");
return Redirect("~/");
}
But the problem is I cannot find a way to logout from IdentityServer. The closer I came was to use
/connect/endsession?id_token_hint=...&post_logout_redirect_uri=https://myapp.com
But I could not find a way to get raw id_token in code. In the About() method given above I am only getting the claims (which I think is the decrypted contents of id_token) and in those claims list there is no id_token to be seen. But somehow managed to get the id_token from fiddler at this url http://localhost:55383/signin-oidc and then the logout at identityServer triggered(with the help of the url given above).
I have the following questions:
How to get id_token in code? (instead of manual copy from fiddler)
Is there a better way to logout? Or is there an AspnetCore/Oidc framework method to logout (which in turn call the correct server api with correct parameters) ?
I was able to logout and login several times but the id_token was seen the same on fiddler. eg: Bob user, Alice user both had the same id_token. Cookie was cleared and each time different user was displayed on the view still the id_token was same. shouldn't the id_token be different for each login/user?
Signout url worked even when I gave a random string as id_token. Does this mean that IdentityServer4 logout functionality do not work based on id_token?
For logging out, did you try-
HttpContext.Authentication.SignOutAsync("oidc");
in your client's Logout Action?

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