I have the following JWT token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiYXVkIjoiRDFDOTBCRUUtOEM1NC00RDZCLThENDYtOTU5NkJGQjRGMjhCIiwic3ViIjoiMTAwMCJ9.GdVBv2LPqKi1NmwPnnaGq6jz5WMLNkQOG9WttoH6Nfw=
and the secret is
BE7B9DD1-6197-4684-A232-5D680812ADC3
We get the following if we base64encode the secret
QkU3QjlERDEtNjE5Ny00Njg0LUEyMzItNUQ2ODA4MTJBREMz
It verifies with signature in jwt.io
The header is decoded to
{
"typ": "JWT",
"alg": "HS256"
}
and the payload
{
"iss": "http://localhost",
"aud": "D1C90BEE-8C54-4D6B-8D46-9596BFB4F28B",
"sub": "1000"
}
It's all good. I've learned from the following articles
https://auth0.com/docs/server-apis/webapi-owin and
http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/
so the code I have in my Startup.ConfigureOAuth() is just a copy from theirs (Can't remember which one I saw first):
var issuer = "http://localhost";
var audience = "D1C90BEE-8C54-4D6B-8D46-9596BFB4F28B";
var secret = TextEncodings.Base64Url.Decode("BE7B9DD1-6197-4684-A232-5D680812ADC3");
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = context =>
{
context.Ticket.Identity.AddClaim(new System.Security.Claims.Claim("newCustomClaim", "newValue"));
return Task.FromResult<object>(null);
}
}
});
I tired with both Provider and without Provider. But all I got is 401 and the following
{
"Message": "Authorization has been denied for this request."
}
I know my JWT is correct since jwt.io can decode it just fine. Can someone please spot my error or if I should do it differently.
You have copied code from examples that pull base64url encoded secrets from the configuration. Yet in your case the secret is in plain text. So just use:
var secret = "BE7B9DD1-6197-4684-A232-5D680812ADC3";
jwt.io shows that the secret used to sign the JWT you provided was actually the base64encoded variant QkU3QjlERDEtNjE5Ny00Njg0LUEyMzItNUQ2ODA4MTJBREMz that should have been base64decoded first.
Related
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
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
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
I have an OAuth2 token like this...
{{
"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"expires_in": "3600",
"refresh_token": "xxxxxxxxxxxxxxxxxx",
"token_type": "Bearer",
}}
and I'm trying to create a DriveService object...
Service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "foo",
});
(like this?)
but I'm clearly not doing this properly and I'm having trouble finding documentation.
When I attempt to create a GoogleCredential to pass to the DriveService
GoogleCredential credential = GoogleCredential.FromJson(credentialAsSerializedJson).CreateScoped(GoogleDriveScope);
I get the following exception:
{System.InvalidOperationException: Error creating credential from JSON. Unrecognized credential type .
Am I going about this the wrong way entirely?
(This is the sample code context)
I have managed to figure this out.
The solution was to create a Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow with my ClientID and ClientSecret...
Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow googleAuthFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer()
{
ClientSecrets = new ClientSecrets()
{
ClientId = ClientID,
ClientSecret = ClientSecret,
}
});
and also a Google.Apis.Auth.OAuth2.Responses.TokenResponse:
Google.Apis.Auth.OAuth2.Responses.TokenResponse responseToken = new TokenResponse()
{
AccessToken = SavedAccount.Properties["access_token"],
ExpiresInSeconds = Convert.ToInt64(SavedAccount.Properties["expires_in"]),
RefreshToken = SavedAccount.Properties["refresh_token"],
Scope = GoogleDriveScope,
TokenType = SavedAccount.Properties["token_type"],
};
and use each to create a UserCredential that is, in turn, used to initialize the DriveService...
var credential = new UserCredential(googleAuthFlow, "", responseToken);
Service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "com.companyname.testxamauthgoogledrive",
});
I updated the TestXamAuthGoogleDrive test harness project to reflect these changes.
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