GithubHelp home page GithubHelp logo

robisim74 / angularspawebapi Goto Github PK

View Code? Open in Web Editor NEW
230.0 37.0 59.0 5.78 MB

Angular Single Page Application with an ASP.NET Core Web API that uses token authentication

License: MIT License

C# 44.68% HTML 11.12% TypeScript 38.14% JavaScript 1.17% SCSS 4.89%
angular identityserver aspnetcore typescript webapi authentication visual-studio

angularspawebapi's People

Contributors

aalmajanob avatar exp10der avatar robisim74 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angularspawebapi's Issues

Question About Scaling Potential

Hey there, this is a great project. I have a similarly set up project and I have a question. I am running an identity server and a web API in the same project using the resource owner password grant. After taking a look at the README this caught my eye: "If more than one client app requires the Web API, use an interactive flow: IdentityServer4 or the other libraries allow you to scale your application"

When I was working on my application scaling was something I was unsure of. Because identity server and the api are in the same application, if I were to set up multiple servers hosting the api, there would also be multiple instances of the identity server application. Do you see any issues with this scenario? This might be a very open-ended question, but this repo is the best example I've come across so far and you seem very knowledgeable about the subject.

Can't deploy your project

Hey,
First of all, thanks for the template,
It really helped me understand things out with IdentityServer and ASPNetCORE + Angular.

I'm facing an issue when trying to deploy to Azure Web App,
I'm getting error 500, and in the Web App logs I see that it cannot find the index.htm, and therefore it throws the error 500.

Did anyone else faced the same issue?
Maybe you've got a solution?

Question: How to Redirect to IdentityServer instead of using password flow?

Hi There,

Thanks for your great sample. It has super useful info regarding Angular2-jwt usage with IDSvr.

All the samples I have seen thus far, including work from Damien Bod, ( https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow ) has the angular client use implicit flow and redirects to the IdentityServer where the user chooses thier auth type, so they can choose 'local account' or google or whatever is setup on IdSvr.

Is it easy to switch your code from a ROPC flow to Implicit?

I have spent a bit looking at your sample code and that of Damien Bods, however I cannot see what I need to change properly.

In Damiens code the authorize function does a redirect to the IDSvr

public Authorize() {
        this.ResetAuthorizationData();

        console.log('BEGIN Authorize, no auth data');

        let authorizationUrl = this._configuration.server + '/connect/authorize';
        let client_id = this._configuration.client_id;
        let redirect_uri = this._configuration.redirect_url;
        let response_type = this._configuration.response_type;
        let scope = this._configuration.scope;
        let nonce = 'N' + Math.random() + '' + Date.now();
        let state = Date.now() + '' + Math.random();

        this.store('authStateControl', state);
        this.store('authNonce', nonce);
        console.log('AuthorizedController created. adding myautostate: ' + this.retrieve('authStateControl'));

        let url =
            authorizationUrl + '?' +
            'response_type=' + encodeURI(response_type) + '&' +
            'client_id=' + encodeURI(client_id) + '&' +
            'redirect_uri=' + encodeURI(redirect_uri) + '&' +
            'scope=' + encodeURI(scope) + '&' +
            'nonce=' + encodeURI(nonce) + '&' +
            'state=' + encodeURI(state);

        window.location.href = url;
    }

So I'd say I'd need to implement the same redirect with your sample as well.

But.... Im just not sure how to handle the redirect using Auth0.

Anyways, you probably have the answer so I'll leave my question at that. Any assistance or answer on this would be greatly appreciated.

Cheers

Is this the right way to do it?

Hello, thanks for your sample I am able to understand further about such topic. I have a question whether I'm doing it right or wrong.

I am using your sample. However, the difference is the IdentityServer is hosted on its own. The SPA and APIs are hosted on the same server. (Probably separated soon but not now).

Since it's using ROPC, I just get the access tokens with its default claims that I asked for. I may be doing something wrong or missing something, but I tried ProfileService etc just so to force it to spit out the custom claims when I access connect/token. However, it doesn't look like I am on the right path and based on my research. It suggested to let the client use connect/userinfo to get the information. That makes sense. However, my scenario is given the User.Claims from asp.net, I need to be able to know who you are besides giving me the id such as first name or last name. Currently what I'm doing is the following:

        var identityId = User.FindFirst("sub").Value;
        value.UserId = new Guid(identityId);
        var p = await _userManager.FindByIdAsync(identityId);
        var m = await _userManager.GetClaimsAsync(p);

Which works but I'm not sure if it's correct. I wanted to minimize the database calls if the client can just get the claims and pass it through the server. What do you think?

MSSQL dotnet ef database update problem

CREATE TABLE [AspNetRoles] (
    [Id] TEXT NOT NULL,
    [ConcurrencyStamp] TEXT NULL,
    [Name] TEXT NULL,
    [NormalizedName] TEXT NULL,
    CONSTRAINT [PK_AspNetRoles] PRIMARY KEY ([Id])
);: Microsoft.EntityFrameworkCore.Database.Command[200102]
      Failed executing DbCommand (9ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [AspNetRoles] (
          [Id] TEXT NOT NULL,
          [ConcurrencyStamp] TEXT NULL,
          [Name] TEXT NULL,
          [NormalizedName] TEXT NULL,
          CONSTRAINT [PK_AspNetRoles] PRIMARY KEY ([Id])
      );
System.Data.SqlClient.SqlException (0x80131904): Column 'Id' in table 'AspNetRoles' is of a type that is invalid for use as a key column in an index.
Could not create constraint or index. See previous errors.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues)
ClientConnectionId:6d7fc6df-3e63-4c32-9b4f-58fc66967739
Error Number:1919,State:1,Class:16

Can you provide fix?

Cannot get userinfo from userinfo endpoint

I can signin successful, get access_token, but do not have refresh_token.
When the site try to get userinfor via connect/userinfo. It always give 405 error

Response for preflight has invalid HTTP status code 405

What're my missing?
thanks

Fix for AOT compilation and Lazy-Loaded modules

Dear Roberto,

How are you?

I had to perform a little change over your original 'webpack.config.js' file in order to fix a problem which was causing an error when compile in AOT + LazyLoading Modules.

The fix is just to remove the '/app' from the genDir:
'angular-router-loader?aot=true&genDir=aot/app'
to
'angular-router-loader?aot=true&genDir=aot'

Explanation:

When we use a LazyLoaded module: https://angular.io/guide/ngmodule#lazy-loading-modules-with-the-router we need to include its relative path as

{ path: 'values', loadChildren: './values/values.module#ValuesModule' }

and include within the files tag at 'tsconfig-aot.json':

  "files": [
    "app/app.module.ts",
    "app/values/values.module.ts",
    "app/main-aot.ts"
  ],

If we do not perform this fix the AOT routes appears with a duplicate app like '.../app/app/values/values.module...'

I thought you could maybe find it interesting to know in case you include Lazy Loading approach in the future.

Regards,
Andrés.

Question about the AuthGard

Hi, Thanks for providing so good example.

I just got a question, when I try the authGard and refresh the page, and if the networking is slow, then the AuthGard will think current user don't have any roles. It will redirect me to the login page.

I think it's because below getUserInfo is async and it didn't return the result before we call the AuthGard?

@Injectable() export class AuthenticationService {
// On bootstrap or refresh, tries to get user's data. if (this.tokenNotExpired()) { this.getUserInfo().subscribe( (userInfo: any) => { this.changeUser(userInfo); }); }

I'm not sure how to fix this, can you please help take a look?

Thanks.

error.json() is not a function when username/password incorrect

On entering incorrect credentials I get a javascript error, error.json is not a function.
If I change lines 40-42 of signin.ts as follows:

            if (error.error) {
                switch (error.error.error) {
                    case 'invalid_grant':

It works as expected. No idea why it's "error.error.error"!

Requires editing appsettings.json

The provided appsettings.json has a hard-coded path in the connection string that won't work on most developer machines. If you modify to just be "Data Source=IdentityDB.sqlite" and specify that this file should be "Copy If Newer" it works out of the box for everyone.

ASP.NET Core 2

Upgrade to ASP.NET Core 2 when IdentityServer will be ready.

Unable to use migrations

Hi i was trying to "enable-migrations"
i got this error

`Exception calling "SetData" with "2" argument(s): "Type
'Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProject'
in assembly 'Microsoft.VisualStudio.ProjectSystem.VS.Implementation,
Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' is not marked as
serializable."
At C:\Users\Nour
Ahmed.nuget\packages\entityframework\6.2.0\tools\EntityFramework.psm1:720 char:5

  • $domain.SetData('project', $project)
    
  • ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    • FullyQualifiedErrorId : SerializationException

Exception calling "SetData" with "2" argument(s): "Type
'Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProject'
in assembly 'Microsoft.VisualStudio.ProjectSystem.VS.Implementation,
Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' is not marked as
serializable."
At C:\Users\Nour
Ahmed.nuget\packages\entityframework\6.2.0\tools\EntityFramework.psm1:721 char:5

  • $domain.SetData('contextProject', $contextProject)
    
  • ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    • FullyQualifiedErrorId : SerializationException

Exception calling "SetData" with "2" argument(s): "Type
'Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProject'
in assembly 'Microsoft.VisualStudio.ProjectSystem.VS.Implementation,
Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' is not marked as
serializable."
At C:\Users\Nour
Ahmed.nuget\packages\entityframework\6.2.0\tools\EntityFramework.psm1:722 char:5

  • $domain.SetData('startUpProject', $startUpProject)
    
  • ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    • FullyQualifiedErrorId : SerializationException

System.NullReferenceException: Object reference not set to an instance of an object.
at System.Data.Entity.Migrations.Extensions.ProjectExtensions.GetPropertyValue[T](Project project, String propertyName)
at System.Data.Entity.Migrations.MigrationsDomainCommand.GetFacade(String configurationTypeName, Boolean useContextWorkingDirectory)
at System.Data.Entity.Migrations.EnableMigrationsCommand.FindContextToEnable(String contextTypeName)
at System.Data.Entity.Migrations.EnableMigrationsCommand.<>c__DisplayClass2.<.ctor>b__0()
at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)
Object reference not set to an instance of an object.`

any work around for it

Signin - Custom Check before continue with the authorization

Hello Roberto,

First of all thanks for your contribution! I find its code really great and clean.

I am developing an app which follows your authentication and spa design approach. It needs to perform some custom checks once the user starts the signin proccess in order to know if its user email has not been deleted/blocked from the organization Active Directory in order to let him authorize via /connect/token or not.

Where do you consider I should better place this custom bussiness-logic layer?

My first approach has been to create a new backend API Controller which takes the user signin decision, so I could consume it from the client (signin.ts), before calling the 'this.authenticationService.signin(...)' . So if the API Controller did find the user within the Active Directory and it has not been blocked, the authentication will continue. At the same time, if the users email has been deleted/blocked I will remove it also from the Identity database in order to guarantee he wont be able to access anymore.

The problem is that I find this approach easily hackeable just by using the developer tools. Is there any way I could centralize the sigin token login within the backend to make it more secure?

Thanks really much for your time,
Andrés.

Cannot run angular app

When i try to tun npm start, I got this error:

Loading...
ERROR in ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./app/styles.scss
Module build failed: Error: Missing binding F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\vendor\win32-x64-48\binding.node
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 6.x

Found bindings for the following environments:
  - Windows 64-bit with Node.js 5.x

This usually happens because your environment has changed since running `npm install`.
Run `npm rebuild node-sass --force` to build the binding for your current environment.
    at module.exports (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\binding.js:15:13)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\index.js:14:35)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\sass-loader\lib\loader.js:3:14)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
 @ ./app/styles.scss 4:14-118 18:2-22:4 19:20-124
 @ ./app/main.ts
 @ multi webpack-hot-middleware/client?path=%2F__webpack_hmr ./app/main.ts
ERROR in ./app/home/home.component.scss
Module build failed: Error: Missing binding F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\vendor\win32-x64-48\binding.node
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 6.x

Found bindings for the following environments:
  - Windows 64-bit with Node.js 5.x

This usually happens because your environment has changed since running `npm install`.
Run `npm rebuild node-sass --force` to build the binding for your current environment.
    at module.exports (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\binding.js:15:13)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\index.js:14:35)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\sass-loader\lib\loader.js:3:14)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
 @ ./app/home/home.component.ts 14:21-53
 @ ./app/app.module.ts
 @ ./app/main.ts
 @ multi webpack-hot-middleware/client?path=%2F__webpack_hmr ./app/main.ts
ERROR in ./app/resources/resources.component.scss
Module build failed: Error: Missing binding F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\vendor\win32-x64-48\binding.node
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 6.x

Found bindings for the following environments:
  - Windows 64-bit with Node.js 5.x

This usually happens because your environment has changed since running `npm install`.
Run `npm rebuild node-sass --force` to build the binding for your current environment.
    at module.exports (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\binding.js:15:13)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\index.js:14:35)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\sass-loader\lib\loader.js:3:14)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
 @ ./app/resources/resources.component.ts 28:21-58
 @ ./app/resources/resources.module.ts
 @ ./app/app-routing.module.ts
 @ ./app/app.module.ts
 @ ./app/main.ts
 @ multi webpack-hot-middleware/client?path=%2F__webpack_hmr ./app/main.ts
ERROR in ./app/dashboard/dashboard.component.scss
Module build failed: Error: Missing binding F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\vendor\win32-x64-48\binding.node
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 6.x

Found bindings for the following environments:
  - Windows 64-bit with Node.js 5.x

This usually happens because your environment has changed since running `npm install`.
Run `npm rebuild node-sass --force` to build the binding for your current environment.
    at module.exports (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\binding.js:15:13)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\node-sass\lib\index.js:14:35)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (F:\lamemmv\study\self-study\asp.net\AngularSPAWebAPI-master\src\AngularSPAWebAPI\node_modules\sass-loader\lib\loader.js:3:14)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
 @ ./app/dashboard/dashboard.component.ts 38:21-58
 @ ./app/dashboard/dashboard.module.ts
 @ ./app/app-routing.module.ts
 @ ./app/app.module.ts
 @ ./app/main.ts
 @ multi webpack-hot-middleware/client?path=%2F__webpack_hmr ./app/main.ts

What're my missing?
Thanks

Logout doesn't really logging out!

After logout, If you try to use the previous token, it'll work as if you was still logged in.
It should return 401, right?
Is the logic not implemented yet?

Session time question

Hi,

I wondered if there is any logic for configuring session time. I would like to make session 20 minutes and logout user, if user didn't show any activity during this 20 minutes. As I understand, I might configure it thru scheduleTokenRefresh() and unscheduleRefresh() with additional logic. Please correct me if I'm wrong.

Best regards,
Taras

I am not getting given_name returned

I have switched the code to use an Azure SQL Database and I can register and login just fine however when I login I never seem to get the "given_name" returned. I added the EntityFramework Core SQL driver and can see my accounts getting created correctly in the database but when I watch what happens when I login I only get the GUID, Name, and Role fields returned in the JSON.

This is working perfectly when using the SQLite file that comes with the project.

error on 'npm start'

ERROR in .//css-loader!.//sass-loader!./app/styles/blue-amber.scss
Module build failed: Error: Missing binding C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\node-sass\vendor\win32-x64-51\binding.node
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 7.x

Found bindings for the following environments:

  • Windows 32-bit with Node.js 5.x

This usually happens because your environment has changed since running npm install.
Run npm rebuild node-sass to build the binding for your current environment.
at module.exports (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\node-sass\lib\binding.js:15:13)
at Object. (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\node-sass\lib\index.js:14:35)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object. (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\sass-loader\index.js:4:12)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at loadLoader (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\loadLoader.js:13:17)
at iteratePitchingLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:164:2)
at iteratePitchingLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:160:10)
at C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:168:18
at loadLoader (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\loadLoader.js:36:3)
at iteratePitchingLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:164:2)
at runLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:357:2)
at NormalModule.doBuild (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModule.js:131:2)
at NormalModule.build (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModule.js:181:15)
at Compilation.buildModule (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\Compilation.js:129:9)
at factoryCallback (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\Compilation.js:308:10)
at C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModuleFactory.js:243:4
at C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModuleFactory.js:94:13
@ ./app/styles/blue-amber.scss 4:14-127
@ ./app/main.ts

ERROR in .//css-loader!.//sass-loader!./app/styles/app.scss
Module build failed: Error: Missing binding C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\node-sass\vendor\win32-x64-51\binding.node
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 7.x

Found bindings for the following environments:

  • Windows 32-bit with Node.js 5.x

This usually happens because your environment has changed since running npm install.
Run npm rebuild node-sass to build the binding for your current environment.
at module.exports (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\node-sass\lib\binding.js:15:13)
at Object. (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\node-sass\lib\index.js:14:35)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object. (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\sass-loader\index.js:4:12)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at loadLoader (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\loadLoader.js:13:17)
at iteratePitchingLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:164:2)
at iteratePitchingLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:160:10)
at C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:168:18
at loadLoader (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\loadLoader.js:36:3)
at iteratePitchingLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:164:2)
at runLoaders (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\loader-runner\lib\LoaderRunner.js:357:2)
at NormalModule.doBuild (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModule.js:131:2)
at NormalModule.build (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModule.js:181:15)
at Compilation.buildModule (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\Compilation.js:129:9)
at factoryCallback (C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\Compilation.js:308:10)
at C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModuleFactory.js:243:4
at C:\Users\nadav\Desktop\Angular2SPAWebAPI-master\src\Angular2SPAWebAPI\node_modules\webpack\lib\NormalModuleFactory.js:94:13
@ ./app/styles/app.scss 4:14-120
@ ./app/main.ts

question about role claims

Hello Dear,

i'm trying to use role claims , but it doesn't work

first i created role claim by adding these lines of code after created roles

   var adminRole = await _roleManager.FindByNameAsync("administrator");
  await _roleManager.AddClaimAsync(adminRole, new Claim("my_claim_type", value: "my_claim_value"));

and in services.AddAuthorization i added this line

options.AddPolicy("my_policy", policy => policy.RequireClaim("my_claim_type", "my_claim_value"));

then

 [HttpGet]
       // [Authorize(Policy = "my_policy")]
        [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "my_policy")]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

please any help to solve this problem

Set up base urls of configurations

Find a way to set up base urls of configurations (angular-oauth2-oidc & IdentityServer4) depending on your environments:

  • development
  • staging
  • production

In both staging & production envs Angular app should build with ng build --prod

Build fails in VS 2015 Update 3

Hi,
As per your instructions, I've installed the following:

  • The latest version of TypeScript for VS
  • .NET Core 1.1: ^1.0.0-preview2-1-003177
  • Latest VS tools

When I open the project and do a build I get two errors:

  1. npm dependencies are not getting installed ( I see Dependencies - not installed message in Solution Explorer). I've node(v6.2.2) and npm(3.7.1) installed on my machine
  2. Typescript compiler is throwing error messages:
    • It's not recognizing the skipLibCheck, lib, and types options in compilerOptions section of tsconfig.json. So, I removed them. Is it OK?
    • It's trying to compile d.ts files in node_modules folder and throwing a lot of error messages. I tried including the node_modules folder in the exclude section, like so:
      "exclude": [
      "node_modules",
      "aot",
      "app/main-aot.ts"
      ],
      Yet, I keep getting error messages (TS1005 '(' expected.)

Please help me!

Sign out functionality not working correctly

Hello, first thank you so much for your effort on this sample project.

I was reviewing it as in my own implementation of Angular+IdentityServer I am having trouble making the Sign Out functionality fully sign out a user and I was hoping this project was doing that successfully but it seems it suffers the same issue.

When you sign in the server returns a Bearer token to use for subsequent api requests say to the value api. When you sign out the app is sending two requests to /connect/revocation one to revoke the access_token and the other to revoke the refresh_token. These both return 200 as to indicate success however someone with the Bearer token can still login after these two revocations have been called.

You can reproduce this by logging in to the app; grab the bearer token with chrome, make a request with that token to the /api/values with something like Postman and include the Bearer token. You will see that even after Log Out you can still successfully use that token meaning it has not actually be revoked.

This is the same issue I am having in my own project and I was wondering if you are aware of this issue and know of any fix to make your Angular2SPAWebAPI successfully Log Out so that tokens can not be reused after the fact? thank you.

Angular 2 SPA Web API - Explanation

Configuring the ASP.NET Core Web API & IdentityServer4

Let's see what the Config.cs file contains for configuring IdentityServer4. The following is the identification of our client app:

// Clients want to access resources.
public static IEnumerable<Client> GetClients()
{
    // Clients credentials.
    return new List<Client>
    {
        // http://docs.identityserver.io/en/dev/reference/client.html.
        new Client
        {
            ClientId = "Angular2SPA",
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // Resource Owner Password Credential grant.
            AllowAccessTokensViaBrowser = true,
            RequireClientSecret = false, // This client does not need a secret to request tokens from the token endpoint.

            AccessTokenLifetime = 900, // Lifetime of access token in seconds.

            AllowedScopes = {
                IdentityServerConstants.StandardScopes.OpenId, // For UserInfo endpoint.
                IdentityServerConstants.StandardScopes.Profile,
                "WebAPI",
                "roles"
            },
            AllowOfflineAccess = true // For refresh token.

        }
    };
}

As you can see, you can add other clients with their own configuration.
Our Angular 2 app, identified as Angular2SPA:

  • uses ROPC;
  • doesn't use a secret key: in a client application it would be useless because visible;
  • has an access token for 15 minutes, then need to refresh the token;
  • can access to the scopes: in this case our Web API, called with a lot of imagination WebAPI, the OfflineAccess for refresh token
    and OpenId to access to the user's info.
// Identity resources (used by UserInfo endpoint).
public static IEnumerable<IdentityResource> GetIdentityResources()
{
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResource("roles", new List<string> { "role" })
    };
}

// Api resources.
public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("WebAPI" ) {
            UserClaims = { "role" }
        }
    };
}

Note that we can define which user claims will be included in the access token.

Because our Web API is in the same project, in Configure method of Startup.cs file,
we add the authentication middleware:

// IdentityServer4.AccessTokenValidation: authentication middleware for the API.
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
    Authority = "http://localhost:5000/",
    AllowedScopes = { "WebAPI" },

    RequireHttpsMetadata = false
});

We complete the configuration by adding IdentityServer in ConfigureServices method:

// Adds IdentityServer.
// The AddTemporarySigningCredential extension creates temporary key material for signing tokens on every start.
// Again this might be useful to get started, but needs to be replaced by some persistent key material for production scenarios.
// See the cryptography docs for more information: http://docs.identityserver.io/en/release/topics/crypto.html#refcrypto
services.AddIdentityServer()
    .AddTemporarySigningCredential()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    .AddInMemoryClients(Config.GetClients())
    .AddAspNetIdentity<ApplicationUser>(); // IdentityServer4.AspNetIdentity.

The extension method AddAspNetIdentity to use the ASP.NET Identity requires another setting:

// Adds IdentityServer.
app.UseIdentityServer();

Now we can add related services: Identity and for simplicity SQLite.

// Identity & SQLite.
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

and

// Adds Identity.
app.UseIdentity();

Also we define the policy of access to the Web Api controllers.
In our sample, we create two policies:

  • Manage Account only for administrator role;
  • Access Resources for administrator role and for user role.
// Claims-Based Authorization: role claims.
services.AddAuthorization(options =>
{
    // Policy for dashboard: only administrator role.
    options.AddPolicy("Manage Accounts", policy => policy.RequireClaim("role", "administrator"));
    // Policy for resources: user or administrator role. 
    options.AddPolicy("Access Resources", policyBuilder => policyBuilder.RequireAssertion(
            context => context.User.HasClaim(claim => (claim.Type == "role" && claim.Value == "user")
                || (claim.Type == "role" && claim.Value == "administrator"))
        )
    );
});

We add the authorization to the Identity controller, which is used by the dashboard:

[Route("api/[controller]")]
[Authorize(Policy = "Manage Accounts")] // Authorization policy for this API.
public class IdentityController : Controller
...

and to Values controller, that returns the resources for the authenticated users:

[Route("api/[controller]")]
[Authorize(Policy = "Access Resources")] // Authorization policy for this API.
public class ValuesController : Controller
...

Remember: when we have defined our APIResource, we have included the roles with user claim role to allow the user to access the resources.

Finally, we set the startup on the entry point of the client application:

// Microsoft.AspNetCore.StaticFiles: API for starting the application from wwwroot.
// Uses default files as index.html.
app.UseDefaultFiles();
// Uses static file for the current path.
app.UseStaticFiles();

Understanding how it works

It's important to understand and observe how the authentication and authorization services work,
in order to implement the client app.

If you debug the app and navigate the browers to:

http://localhost:5000/.well-known/openid-configuration

you should see the so-called discovery document.
The discovery endpoint can be used to retrieve metadata about IdentityServer.
For an authentication, we need to send the request at the token_endpoint:

http://localhost:5000/connect/token

For example, you can use Postman as client to send this POST request:

POST /connect/token HTTP/1.1
Host: localhost:5000
Content-Type: application/x-www-form-urlencoded

client_id=Angular2SPA&grant_type=password&username=admin%40gmail.com&password=Admin01*&scope=WebAPI+offline_access+openid+profile+roles

Note the Content-Type as x-www-form-urlencoded, and parameters provided in the body. This is the response:

{
    "access_token": "eyJhbGci...",
    "expires_in": 900,
    "token_type": "Bearer",
    "refresh_token": "5007bc4b..."
}

The user has been authenticated, and he has an access token that will expire in 900 seconds, but he has also a refresh token.
You can use a tool like JSON Web Token to decode the JWT and see the payload (with our scope claims):

{
 "nbf": 1480712377,
 "exp": 1480713277,
 "iss": "http://localhost:5000",
 "aud": "http://localhost:5000/resources",
 "client_id": "Angular2SPA",
 "sub": "c0bb2220-8c99-46dc-ad39-b707f37f047f",
 "auth_time": 1480712377,
 "idp": "local",
 "role": "administrator",
 "scope": [
  "offline_access",
  "openid",
  "profile",
  "roles",
  "WebAPI"
 ],
 "amr": [
  "pwd"
 ]
}

Now we can send a GET request to our Web API in this way:

GET /api/values HTTP/1.1
Host: localhost:5000
Authorization: Bearer eyJhbGci...

This request contains a header parameter named Authorization and its value is the bearer token. The response:

[
    "value1",
    "value2"
]

And when, past the 15 minutes, the token expires? The user can no longer access resources.
You can ask him to sign in again, or handle a refresh token strategy: to get a new access token,
you can send a POST request, with grant_type set to refresh_token and refresh token as parameters:

POST /connect/token HTTP/1.1
Host: localhost:5000
Content-Type: application/x-www-form-urlencoded

client_id=Angular2SPA&grant_type=refresh_token&refresh_token=5007bc4b...

Implementing the Angular 2 SPA

Ok, how do we transform the requests done via Postman in an Angular 2 app?

In this sample, to send unauthenticated requests for signing in and signing up the user, we use the Angular 2 http module,
as in AuthenticationService class:

/**
 * Tries to sign in the user.
 *
 * @param username
 * @param password
 * @return The user's data
 */
public signin(username: string, password: string): Observable<any> {

    // Token endpoint & params.
    let tokenEndpoint: string = Config.TOKEN_ENDPOINT;

    let params: any = {
        client_id: Config.CLIENT_ID,
        grant_type: Config.GRANT_TYPE,
        username: username,
        password: password,
        scope: Config.SCOPE
    };

    // Encodes the parameters.
    let body: string = this.encodeParams(params);

    this.authTime = new Date().valueOf();

    return this.http.post(tokenEndpoint, body, this.options)
        .map((res: Response) => {

            let body: any = res.json();

            // Sign in successful if there's an access token in the response.
            if (typeof body.access_token !== 'undefined') {

                // Stores access token & refresh token.
                this.store(body);

                // Gets user info.
                this.userInfo();

                // Tells all the subscribers about the new status.
                this.signinSubject.next(true);

            }

        }).catch((error: any) => {

            // Error on post request.
            return Observable.throw(error);

        });

}

We send a request to UserInfo endpoint to get the user's data
using angular2-jwt library, that builds for us the header with the authorization token:

/**
 * Calls UserInfo endpoint to retrieve user's data.
 */
private userInfo(): void {

    if (this.tokenNotExpired()) {
        this.authHttp.get(Config.USERINFO_ENDPOINT)
            .subscribe(
            (res: any) => {

                let user: any = res.json();
                let roles: string[] = user.role;

                // Tells all the subscribers about the new data & roles.
                this.userSubject.next(user);
                this.rolesSubject.next(user.role);

            },
            (error: any) => {

                // Need to handle this error.
                console.log(error);

            });
    }

}

In this example, we use a scheduler to request a new access token before it expires through the refresh token:

/**
 * Optional strategy for refresh token through a scheduler.
 *
 * Will schedule a refresh at the appropriate time.
 */
public scheduleRefresh(): void {

    let source = this.authHttp.tokenStream.flatMap(
        (token: string) => {

            let delay: number = this.expiresIn - this.offsetSeconds * 1000;

            return Observable.interval(delay);

        });

    this.refreshSubscription = source.subscribe(() => {
        this.getNewToken().subscribe(
            () => {
                // Scheduler works.
            },
            (error: any) => {
                // Need to handle this error.
                console.log(error);
            }
        );
    });

}

/**
 * Case when the user comes back to the app after closing it.
 */
public startupTokenRefresh(): void {
        
    // If the user is authenticated, uses the token stream
    // provided by angular2-jwt and flatMap the token.
    if (this.signinSubject.getValue()) {

        let source = this.authHttp.tokenStream.flatMap(
            (token: string) => {
                let now: number = new Date().valueOf();
                let exp: number = Helpers.getExp();
                let delay: number = exp - now - this.offsetSeconds * 1000;

                // Uses the delay in a timer to run the refresh at the proper time. 
                return Observable.timer(delay);
            });

        // Once the delay time from above is reached, gets a new JWT and schedules additional refreshes.
        source.subscribe(() => {
            this.getNewToken().subscribe(
                () => {
                    this.scheduleRefresh();
                },
                (error: any) => {
                    // Need to handle this error.
                    console.log(error);
                }
            );
        });

    }

}

/**
 * Unsubscribes from the scheduling of the refresh token.
 */
public unscheduleRefresh(): void {

    if (this.refreshSubscription) {
        this.refreshSubscription.unsubscribe();
    }

}

/**
 * Tries to get a new token using refresh token.
 */
public getNewToken(): Observable<any> {

    let refreshToken: string = Helpers.getToken('refresh_token');

    // Token endpoint & params.
    let tokenEndpoint: string = Config.TOKEN_ENDPOINT;

    let params: any = {
        client_id: Config.CLIENT_ID,
        grant_type: "refresh_token",
        refresh_token: refreshToken
    };

    // Encodes the parameters.
    let body: string = this.encodeParams(params);

    this.authTime = new Date().valueOf();

    return this.http.post(tokenEndpoint, body, this.options)
        .map((res: Response) => {

            let body: any = res.json();

            // Successful if there's an access token in the response.
            if (typeof body.access_token !== 'undefined') {

                // Stores access token & refresh token.
                this.store(body);

            }

        }).catch((error: any) => {

            // Error on post request.
            return Observable.throw(error);

        });

}

To send authenticated requests, as in ResourcesComponent class, we still use angular2-jwt library:

// Sends an authenticated request.
this.authHttp.get("/api/values")
    .subscribe(
    (res: any) => {

        this.values = res.json();

    },
    (error: any) => {

        console.log(error);

    });

Building the Angular 2 app with AoT compilation & webpack

For production, we build the Angular 2 app through ngc compiler & webpack.
To do this, after the AoT compilation, in webpack.config.js file we set as entry point main-aot.ts:

// In production mode, we use AoT compilation & minification.
module.exports = {
    entry: {
        'app-aot': './app/main-aot.js'
    },
	...

and as output the wwwroot folder (as set in Startup.cs):

output: {
    path: "./wwwroot/",
    filename: "dist/[name].bundle.js",
    chunkFilename: 'dist/[name].chunk.js'
},

Finally, we ask webpack to insert the script of the bundle in our index.html:

// Adds script for the bundle in index.html.
new HtmlWebpackPlugin({
    filename: 'index.html',
    inject: 'body',
    template: 'app/index.html'
})

Multiples IdentityServer4.Models.Client

Hello yor example is very good and clear.
i see that you register manually and in memory the client called AngularSPA, in Config.cs file.
.......
// Clients credentials.
return new List
{
// http://docs.identityserver.io/en/release/reference/client.html.
new Client
{
ClientId = "AngularSPA",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // Resource Owner Password Credential grant.
AllowAccessTokensViaBrowser = true,
RequireClientSecret = false, // This client does not need a secret to request tokens from the token endpoint.

                AccessTokenLifetime = 900, // Lifetime of access token in seconds.

                AllowedScopes = {
                    IdentityServerConstants.StandardScopes.OpenId, // For UserInfo endpoint.
                    IdentityServerConstants.StandardScopes.Profile,
                    "roles",
                    "WebAPI"
                },
                AllowOfflineAccess = true, // For refresh token.
                RefreshTokenUsage = TokenUsage.OneTimeOnly,
                AbsoluteRefreshTokenLifetime = 7200,
                SlidingRefreshTokenLifetime = 900,
                RefreshTokenExpiration = TokenExpiration.Sliding
            }
        };

.......
if i need to register other client and then save in database?
other question is is how get a current user in a controller?
Thank so much.

Refresh token

Thank you for great sample! It is interesting to know how it is possible to implement refresh_token to use short-lived access tokens and update them using refresh_tokens, that can be revoked

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.