Time triggered Azure function - Bearer token generation to call a protected API - oauth-2.0

I am trying to write an Azure function which is time triggered and runs every 10 minutes.
The function needs to call an API which expects a bearer token.
How do I generate the token? Since it is time based, I can't have a user to login and give function authorization token by signing into MS Identity platform which can be used to get the access token.

You just need to get the token by the code below in your timer trigger function:
HttpClient client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "<your app client id>" },
{ "scope", "<scope>" },
{ "username", "<your user name>" },
{ "password", "<your password>" },
{ "grant_type", "password" },
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://login.microsoftonline.com/<your tenant id>/oauth2/v2.0/token", content);
var responseString = await response.Content.ReadAsStringAsync();
Then you need to parse responseString(in json type) and use the access token in it to request your api.
Update:
Get token by client credential:
HttpClient client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "<your app client id>" },
{ "scope", "<scope>" },
{ "client_secret", "<your app client secret>" },
{ "grant_type", "client_credentials" },
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://login.microsoftonline.com/<your tenant id>/oauth2/v2.0/token", content);
var responseString = await response.Content.ReadAsStringAsync();

You can use a password grant flow but this requires you to provide user and password to your application. A better approach is to do the Auth outside of your application using device code flow.
See this repo for an example:
https://github.com/blueboxes/devicecodesample

Related

How to configure Identity Server with NSwag API using IIdentityServerBuilder's .AddApiAuthorization()?

I'd like to create an authentication/authorization flow of sorts using Identity Server to have a user authorize themselves in my Swagger API so that they may access endpoints marked with the [Authorize] attribute. This is the current flow I have:
I have Swagger set up with the NSwag middleware with the OAuth2 security scheme:
services.AddMvcCore().AddApiExplorer();
services.AddOpenApiDocument(settings =>
{
settings.Title = "MyProject Services";
settings.Version = "1.0";
settings.AddSecurity("oauth2", new NSwag.OpenApiSecurityScheme
{
Type = NSwag.OpenApiSecuritySchemeType.OAuth2,
Flow = NSwag.OpenApiOAuth2Flow.AccessCode,
AuthorizationUrl = "/connect/authorize",
TokenUrl = "/connect/token",
Scopes = new Dictionary<string, string>
{
{ "MyProjectServicesAPI", "API Access" }
}
});
settings.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("oauth2"));
});
And the OAuth2 client settings in Configure():
app.UseOpenApi();
app.UseSwaggerUi3(options =>
{
options.OAuth2Client = new NSwag.AspNetCore.OAuth2ClientSettings
{
ClientId = "MyProjectAPI",
ClientSecret = "mysecret",
UsePkceWithAuthorizationCodeGrant = true
};
});
After a user selects the scope and authorizes, they get redirected to my Identity Server Login Page I scaffolded and from there they can login. Once they put in their credentials and press, 'Login', they then get redirected back to the Swagger API. So far so good. Now this is where I start to have trouble cause I would like to later add policies so a user must have a specific claim to access an endpoint, but right now, I'm not able to see any of my user's claims in the JWT Bearer token that's in the request header when I access and endpoint. The only information I get about my user is in the 'sub' which is their GUID. I'd like to be able to get their username, email, and role(s) as well.
This is what I have setup for Identity Server so far (and where I'm currently stuck):
Under ConfigureServices():
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources = new IdentityResourceCollection
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource
{
Name = "roles",
DisplayName = "roles",
UserClaims = new List<string> { JwtClaimTypes.Role }
},
new IdentityResource
{
Name = "basicInfo",
DisplayName = "basic info",
UserClaims = new List<string> {
JwtClaimTypes.PreferredUserName
}
}
};
options.Clients = new ClientCollection
{
new Client
{
ClientId = "MyProjectAPI",
ClientName = "My Project Services API",
ClientSecrets = { new Secret("mysecret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "https://localhost:44319/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { "https://localhost:44319/Identity/Account/Logout" },
AllowedScopes = {
"basicInfo",
"roles",
"MyProjectServicesAPI",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
RequirePkce = true,
RequireConsent = false
}
};
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerJwt()
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true
};
});
And then in the pipeline:
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
I recently got this error that's being thrown from Identity Server's OidcConfigurationController:
'Can't determine the type for the client 'MyProject''
I'm putting the Authorization Code type for the AllowedGrantTypes in my client so I'm not quite sure why it's throwing that error.
Do I need to be adding the claims to the Bearer token myself? If I'm including the scopes, why aren't those claims showing up? Thank you in advance for any assistance.
EDIT #1: I did resolve the error I was receiving from the OidcConfigurationController. I will add the JWT Bearer token only shows the 'MyProjectServicesAPI" scope and nothing else. However, my oidc discovery doc shows all of them?
I think I was able to partially solve my problem. So I didn't have Identity Server's Profile Service set up to grab my user's ID so it could grab the identity claims.
ProfileService.cs:
private readonly UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
// Add custom claims to access token.
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.IssuedClaims.AddRange(context.Subject.Claims);
var user = await _userManager.GetUserAsync(context.Subject);
var roles = await _userManager.GetRolesAsync(user);
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, user.Email),
new Claim(JwtClaimTypes.PreferredUserName, user.UserName),
};
foreach (var claim in claims)
{
context.IssuedClaims.Add(claim);
}
foreach (var role in roles)
{
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, role));
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null) && user.LockoutEnabled;
}
And then back in Startup.cs:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
....
})
.AddProfileService<ProfileService>();
And that's it! Order does matter as I did have AddProfileService() before my AddApiAuthorization() and that didn't work. All of my scopes still aren't showing in my JWT token, so I will need to revisit that, even though the right claims are being pulled from those Identity resources.

No auth header sent by swagger UI using Swashbuckle with OAuth code flow in .NET 6

I am trying to get OAuth code flow with PCKE to work with Swashbuckle (6.2.3) and swagger ui in .NET 6. There are a few things that happen successfully:
In swagger UI I can click on "Authorize" button and get redirected to Azure for login.
The redirect successfully returns to swagger ui and I can see in the network tab that the token is retrieved from Azure by swagger ui.
The problem is when I try to call the sample weather forecast API using swagger UI, no token is attached to the authorization header and it looks like this in the request:
authorization: Bearer undefined
And here is my code:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C"));
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
const string oAuth2 = "oauth2";
options.AddSecurityDefinition(oAuth2, new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(builder.Configuration["AzureAdB2C:AuthorizationUrl"]),
TokenUrl = new Uri(builder.Configuration["AzureAdB2C:TokenUrl"]),
Scopes = {{"openid", "Sign users in"}, {"offline_access", "Maintain access to data you have given it access to"}}
}
},
In = ParameterLocation.Header,
BearerFormat = "JWT",
Scheme = "bearer",
Name = "authorization"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = oAuth2,
Type = ReferenceType.SecurityScheme
},
}, new List<string> {"openid", "offline_access"}
}
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.OAuthClientId(builder.Configuration["AzureAdB2C:ClientId"]);
options.OAuthScopes("openid", "offline_access");
options.OAuthUsePkce();
});
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
I'm not sure what I'm missing. Any ideas?
UPDATE:
I have been able to get it to work with something like this:
options.UseRequestInterceptor("(req) => { req.headers['Authorization'] = 'Bearer ' + window?.swaggerUIRedirectOauth2?.auth?.token?.id_token; return req; }");
But it doesn't look like a proper solution.
You can specify in the OpenApiSecurityScheme to use the id_token instead the access_token that is the default by adding it to the Extensions:
Extensions =
{
// Setting x-tokenName to id_token will send response_type=token id_token and the nonce to the auth provider.
// x-tokenName also specifieds the name of the value from the response of the auth provider to use as bearer token.
{ "x-tokenName", new OpenApiString("id_token") }
}
Source: https://github.com/inouiw/SwaggerUIJsonWebToken/blob/master/Program.cs

Cortana skill authentication

I have enabled the connected service in my Cortana channel (Microsoft) and got the token to the BOT framework.
Now, I want to retrieve the user details from the token by using the registered client id and secret
Sample code in BOT framework:
var authInfo = ((Activity)context.Activity).Entities.FirstOrDefault(e => e.Type.Equals("AuthorizationToken"));
var token = authInfo.Properties["token"].ToString();
Any thoughts?
Check BotAuth out. You can retrieve the token choosing a provider:
const botauth = require("botauth");
const DropboxOAuth2Strategy = require("passport-dropbox-oauth2").Strategy;
...
// Initialize with the strategies we want to use
var auth = new botauth.BotAuthenticator(server, bot, {
secret : "something secret",
baseUrl : "https://" + WEBSITE_HOSTNAME }
);
// Configure the Dropbox authentication provider using the passport-dropbox strategy
auth.provider("dropbox",
function(options) {
return new DropboxOAuth2Strategy(
{
clientID : DROPBOX_APP_ID,
clientSecret : DROPBOX_APP_SECRET,
callbackURL : options.callbackURL
},
function(accessToken, refreshToken, profile, done) {
profile.accessToken = accessToken;
profile.refreshToken = refreshToken;
done(null, profile);
}
);
}
);
If you just want to retrieve user name and ID you can get it from userData object:
UserInfo : { "Name": { "GivenName": "XYZ", "FamilyName": "ABC" }, "Id": "something#outlook.com" }
https://github.com/Microsoft/BotBuilder/issues/3242

IdentityServer 3 not validating secret, "invalid_client" response: Implicit flow, revocation endpoint

I'm using oidc-client in my SPA application against IdentityServer3, using implicit flow.
Revoking my reference token on sign out is not working.
I have set revokeAccessTokenOnSignout: true and client_secret to a dummy secret, and I see the request being sent out. However I'm getting a "400 bad request invalid client" response.
Here's the request being sent:
And here's the response:
{"error":"invalid_client"}
And here's the IdentityServer config:
new Client
{
ClientName = "Client Name",
ClientId = "myclientid",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AccessTokenType = AccessTokenType.Reference,
Flow = Flows.Implicit,
RequireConsent = false,
RedirectUris = new List<string>
{
...
},
AllowedCorsOrigins = new List<string>
{
...
},
PostLogoutRedirectUris = new List<string>
{
...
},
AllowedScopes = new List<string>
{
...
} //.. allowed
}, //..new client
Any ideas what I might be doing wrong?

error:invalid_client - IdentityServer Flow.ClientCredential

I'm having a Client in my IdentityServer3
new Client
{
ClientName = "Client Credentials Flow Client with Client Certificate",
ClientId = "certclient",
ClientSecrets = new List<Secret>
{
new Secret
{
Value = "61B754C541BBCFC6A45A9E9EC5E47D8702B78C29",
Type = Constants.SecretTypes.X509CertificateThumbprint,
}
},
Flow = Flows.ClientCredentials,
AllowedScopes = new List<string>
{
"read",
"write"
},
}
I hosted the Token Service in my local IIS and I tried to ping the Token using Postman, but it given an error {"error":"invalid_client"}
Host URL:
https://localhost:5775/core/connect/token
Header:
Content-Type:application/x-www-form-urlencoded
Authorization:Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Body:
grant_type=client_credentials
&cliend_id=certclient
&client_secret=61B754C541BBCFC6A45A9E9EC5E47D8702B78C29
Note: I'm using pure IdentityServer3 package not Thinktecture

Resources