Google Auth error getting access token in Blazor - asp.net-core-webapi

I currently have a ASP.Net Core Web Api with Blazor WASM which can login successfully to Google OAuth using the component RemoteAuthenticatorView. My intention is now to pass the token I have to the web api, which hopefully can be used to authenticate with the web api. The issue is that TokenProvider.RequestAccessToken() produces the following error.
blazor.webassembly.js:1 Microsoft.JSInterop.JSException: An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 88.. See InnerException for more details.
System.Text.Json.JsonException: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 88.
var tokenResult = await TokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token.Value);
var response = await Http.SendAsync(requestMessage);
}
Program.cs
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Local", options.ProviderOptions);
options.ProviderOptions.DefaultScopes.Add("email");
});
Any ideas? Is a scope missing? I can see id_token in Session storage... Perhaps any net 5 example of Blazor WASM and core web api?
Edit:
I see there is a network call made by RequestAccessToken to google auth, like if it were trying to authenticate again

Create a WebAssembly Blazor App standalone with individual accounts.
Replace the content in the appsettings.json file with the following code:
{
"Google": {
"Authority": "https://accounts.google.com/",
"ClientId": "11111111111-11111111111111111111111111111111.apps.googleusercontent.com",
"DefaultScopes": [ "email" ], // The Blazor WebAssembly template automatically configures default scopes for openid and profile
"PostLogoutRedirectUri": "https://localhost:44313/authentication/logout-callback",
"RedirectUri": "https://localhost:44313/authentication/login-callback",
"ResponseType": "id_token token"
}
}
Run your app, and click on the Login link...You're being redirected to Google' Login page. Authenticate with Google...You are being redirected to your app, the Login is changed into Logout, and your user name appears near it.
You may view the id_token and profile via the developers' tools.
How to retrieve the access token ?
Add the following code snippet to your Index page and run...
#page "/"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
#inject IAccessTokenProvider TokenProvider
<p>#output</p>
<button #onclick="DisplayToken">Display Access Token </button>
#code
{
private string output;
private async Task DisplayToken()
{
var tokenResult = await TokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
output = token.Value;
}
}
}
Note: The code above is for demonstration purposes only (following your code sample). However, if you perform an HTTP call to your Web Api using the HttpClient service the access token is automatically retrieved and assigned to the Authorization header by the AuthorizationMessagelHandler object.

Related

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

Twilio client.notify returns 403

I need to send notifications in my web app (nextjs) when a user sends a message. I followed this but it dowsn't work.
I have the following code:
export const notifyUser = async (_identity: string, _message: string) => {
return await client.notify
.services(config.TWILIO_CHAT_SERVICE_SID)
.notifications.create({ body: _message, identity: [_identity] });
};
I call an api notify-user and I invoke notifyUSer(identity, message)
Identity is the user_id I use to generate the token with the following method:
export const tokenGenerator = (identity: string) => {
// Create an access token which we will sign and return to the client
const token = new AccessToken(
config.TWILIO_ACCOUNT_SID,
config.TWILIO_API_KEY,
config.TWILIO_API_SECRET
);
// Assign the provided identity or generate a new one
token.identity = identity || 'unknown';
if (config.TWILIO_CHAT_SERVICE_SID) {
// Create a "grant" which enables a client to use IPM as a given user,
// on a given device
const chatGrant = new ChatGrant({
serviceSid: config.TWILIO_CHAT_SERVICE_SID,
pushCredentialSid: config.TWILIO_FCM_CREDENTIAL_SID,
});
token.addGrant(chatGrant);
}
if (config.TWILIO_SYNC_SERVICE_SID) {
// Point to a particular Sync service, or use the account default to
// interact directly with Functions.
const syncGrant = new SyncGrant({
serviceSid: config.TWILIO_SYNC_SERVICE_SID || 'default',
});
token.addGrant(syncGrant);
}
// Serialize the token to a JWT string and include it in a JSON response
return {
identity: token.identity,
token: token.toJwt(),
};
};
I expected to get the response that is in the example but I get the following error:
What do you think I did wrong?
Twilio Notify is a different service to Twilio Chat, so you are trying to use incorrect credentials to call the API. You are passing a Chat service sid, not a Notify service sid, and using the chat identity and not the identity of a Notify binding.
If you are trying to send notifications based on chat messages, you should follow this guide on push notification configuration for chat.
If you are trying to send notifications outside of chat using Twilio Notify, follow this guide.
It has something to do with your authentication for notifications. The 403 error is similar to the 401 unauthorized error but the difference is that the 403 error doesn't allow you to re-authenticate and forbids the request. Check through your authentication part.
You can also check the Twilio debugging guide : https://www.twilio.com/docs/usage/troubleshooting/debugging-your-application

AzureAD signout error mesage: "Hmm... we're having trouble signing you out."

I created a web application using ASP.NET CORE, using the quickstart guide in the AzureAD portal. Signing in works, but when I attempt to log out, after about 5 seconds, I get the following message:
https://i.imgur.com/RhOGaf6.png
My app has been registered with the following redirect URIs:
https://i.imgur.com/CAnQpM8.png
With https://localhost:52410/signout-oidc as logout URL, and implicit grand for ID tokens enabled.
I can see in the browser debug menu under network that there is no response
from the logout URL. So, I'm assuming that the error message pop ups because the logout URL takes too long to respond.
Note: If I reload the browser page with the error I do logout.
So I'm wondering how can I resolve this error message?
I solved this error by adjusting the launchSettings.json file.
I adjusted the iisExpress setting in iisSettings to use SSL like so:
"iisExpress": {
"applicationUrl": "http://localhost:3110/",
"sslPort": 44321
}
On top of this, I adjusted the port of my own application to also use 3110.
It seems that the configurations are fine. Here is an working sample for your reference. You can check the code for signing out.
[\[HttpGet\]
public IActionResult SignOut()
{
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
\[HttpGet\]
public IActionResult SignedOut()
{
if (User.Identity.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToAction(nameof(HomeController.Index), "Home");
}
return View();
}][1]

Single Page Application with adal.js and external web api (with AAD authentication)

I have an ASP.NET SPA with a adal-js based authentication, and an ASP.NET Web Api website with Azure Active Directory auth
Both websites are hosted on Azure, on different hostnames, say
https://foo.azurewebsites.com/ and https://fooapi.azurewebsites.com/
The Web Api website auth is configured as
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters() { ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] },
Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
});
}
}
and Main SPA adal.js is initialized as:
var config = {
instance: "https://login.microsoftonline.com/",
tenant: "mytenant",
clientId: "client id of foo registration",
postLogoutRedirectUri: "https://foo.azurewebsites.com/",
cacheLocation: "localStorage"
};
authContext = new AuthenticationContext(config);
// Check For & Handle Redirect From AAD After Login
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
var errorMessage = authContext.getLoginError();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// Check if View Requires Authentication
if (!authContext.getCachedUser()) {
authContext.config.redirectUri = window.location.href;
authContext.login();
return;
}
The Tenant is the same for foo and fooapi, the client id is different (one for each app registration).
The authentication flow in the foo web app is performed successfully, but every http request to fooapi returns 401 unauthorized.
How can I make fooapi share the successful authentication of foo ?
Thank you for any hint
You can use the implicit grant flow in AAD so that an ID Token is received and sent in auth header when API call is made. See below links for the details and sample code.
https://azure.microsoft.com/en-gb/documentation/articles/active-directory-authentication-scenarios/#single-page-application-spa
https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp
How you acquire the access token for the web API?
To make sure the request successfully, you need to acquire the token using the resource you config in web API. You can pass the token from here to check whether the aud claim is equal to the value ida:Audience.
And also make sure the token is issued from the tenant you config in web API project since you didn't ignore the tenant verification.
Please configure your web point into endpoints and add it to initialization.
var endpoints = {`enter code here`
"https://yourhost/api": "b6a68585-5287-45b2-ba82-383ba1f60932",
};
adalAuthenticationServiceProvider.init(
{
// Config to specify endpoints and similar for your app
tenant: "52d4b072-9470-49fb-8721-bc3a1c9912a1", // Optional by default, it sends common
clientId: "e9a5a8b6-8af7-4719-9821-0deef255f68e", // Required
//localLoginUrl: "/login", // optional
//redirectUri : "your site", optional
endpoints: endpoints // If you need to send CORS api requests.
},
$httpProvider // pass http provider to inject request interceptor to attach tokens
);

Thinktecture IdentityServer v3 with WebForms and WebApi

I am trying for a while to figure out how to solve SSO (Single Sign On) with Thinktecture IdentityServer v3 for a legacy webforms application. Unfortunately I am stacked.
The infrastructure is like this:
A WebForm App which need authentication and Authorization (possibly
cookie or bearer token)
A javascript lightweight app (once the user is authenticated) makes requests to an WebApi (which is on separate domain)
I am having the following questions which hopefully will help me to bring things up:
I can't make the legacy webforms application to redirect to IdentityServer, even with set in the Web.Config. I have in the Startup.cs the app.UseCookieAuthentication(....) and app.UseOpenIdConnectAuthentication(....) correctly set ( I guess ). For MVC the [Authorize] attribute force the redirection to the IdentityServer. How this should be done for webforms?
Is there a way once the user is logged in, to reuse the token stored in the cookie as bearer token to the WebApi calls, made from the javascript client. I just want to do the requests to the WebApi on behalf on currently logged user (once again the webforms app and the webapi are on different domains)
Any help will be much appreciated.
Thanks!
I'm currently working on the same type of project. This is what I have found out so far.
There is 4 Separate Concerns.
Identity Server - Maintains Authenticating Users / Clients / Scope
WebApi - Consumes Token generated by Identity Server for Authorization & Identity Information of User.
WebForms / JQuery - For my project currently handles authentication for existing functionality redirects to the new WebApi.
HTML using Javascript - Strictly uses WebApi for Information.
The custom grant below is for a user currently logged in through the WebForm as a membership object & I do not want to ask the user again to relogin via Identity Server.
For direct oAuth Authentication check out the sample here..
Sample Javascript Client
Configuring the Javascript an Implicit Flow would work just fine. Save the token connect with the api.
Identity Server v3
I had to configured using
Custom Grant w IUserService
Custom Grants
These will show how to configure a custom grant validation. With the user service you can have the Identity Service query existing users & customize claims.
There is alot of configuration to the Identity Server to make it your own. this is al very well documented on the IdentityServer website I wont go in how to set the basics up.
Ex: Client Configuration
return new List<Client>
{
new Client
{
ClientName = "Custom Grant Client",
Enabled = true,
ClientId = "client",
ClientSecrets = new List<ClientSecret>
{
new ClientSecret("secret".Sha256()),
},
Flow = Flows.Custom,
CustomGrantTypeRestrictions = new List<string>
{
"custom"
}
}
};
WebApi - Resource
Example
WebApi Client Sample
Need to have the Nuget package
Thinktecture.IdentityServer.AccessTokenValidation
Startup.cs
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
//Location of your identity server
Authority = "https://localhost:44333/core"
});
WebForms
BackEnd WebForms Call
Need Nuget Package
Thinktecture.IdentityModel.Client
[WebMethod]
[ScriptMethod(ResponseFormat.Json)]
public static string AuthorizeClient()
{
var client = new OAuth2Client(
//location of identity server, ClientId, ClientSecret
new Uri("http://localhost:44333/core/connect/token"),
"client",
"secret");
//ClientGrantRestriction, Scope (I have a Client Scope of read), Listing of claims
var result = client.RequestCustomGrantAsync("custom", "read", new Dictionary<string, string>
{
{ "account_store", "foo" },
{ "legacy_id", "bob" },
{ "legacy_secret", "bob" }
}).Result;
return result.AccessToken;
}
These are generic claim for this example however I can generate my own claim objects relating to the user to send to the Identity Server & regenerate an Identity for the WebApi to consume.
WebForms / JQuery
using
JQuery.cookie
$('#btnTokenCreate').click(function (e) {
//Create Token from User Information
Ajax({
url: "Default.aspx/AuthorizeClient",
type: "POST"
},
null,
function (data) {
sendToken = data.d;
//Clear Cookie
$.removeCookie('UserAccessToken', { path: '/' });
//Make API Wrap Info in Stringify
$.cookie.json = true;
//Save Token as Cookie
$.cookie('UserAccessToken', sendToken, { expires: 7, path: '/' });
});
JQuery WebAPI Ajax
Sample Ajax Method - Note the beforeSend.
function Ajax(options, apiToken, successCallback) {
//Perform Ajax Call
$.ajax({
url: options.url,
data: options.params,
dataType: "json",
type: options.type,
async: false,
contentType: "application/json; charset=utf-8",
dataFilter: function (data) { return data; },
//Before Sending Ajax Perform Cursor Switch
beforeSend: function (xhr) {
//Adds ApiToken to Ajax Header
if (apiToken) {
xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", " Bearer " + apiToken);
}
},
// Sync Results
success: function (data, textStatus, jqXHR) {
successCallback(data, textStatus, jqXHR);
},
//Sync Fail Call back
error: function (jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
}
});
}
AngularJS
This has same idea as the JQuery using the
module.run(function($http) {
//Make API Wrap Info in Stringify
$.cookie.json = true;
//Save Token as Cookie
var token = $.cookie('UserAccessToken');
$http.defaults.headers.common.Authorization = 'Bearer ' + token });
This makes the assumption your using the same domain as the WebForm. Otherwise I would use a Query string for a redirect to the Angular page with the token.
For CORS support need to make sure the WebApi has Cors configured for proper functionality. using the
Microsoft.AspNet.WebApi.Cors
Hope this sheds some light on the subject of how to approach this

Resources